use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::core::clock::{ClockForest, DomainId, GlobalTime};
use crate::core::device::{Deferred, Device, DeviceClass, ResetKind};
use crate::core::error::{Error, Result};
use crate::core::record::Recorder;
use crate::core::sched::{
Budget, Consumed, Event, EventId, EventTarget, HostClock, LazyDevice, LazyId, QuantumReport,
Runnable, RunnableId, Scheduler, SchedulerSnapshot,
};
use crate::core::space::{AddressSpace, RequesterId};
use crate::core::state::{MachineShape, Migrations, Sink, Source, StateReader, StateWriter};
use crate::core::wire::{Level, Wire, WireId};
use crate::machine::realize::Instance;
pub const CLOCK_PATH: &str = "/clock";
pub const CLOCK_CLASS: &str = "machine.clock";
pub const WIRE_PATH: &str = "/wires";
pub const WIRE_CLASS: &str = "machine.wires";
pub const SCHED_PATH: &str = "/sched";
pub const SCHED_CLASS: &str = "machine.sched";
pub const MACHINE_STATE_VERSION: u32 = 1;
#[derive(Debug)]
pub struct SpaceEntry {
name: String,
space: Arc<AddressSpace>,
}
impl SpaceEntry {
pub fn name(&self) -> &str {
&self.name
}
pub fn space(&self) -> &Arc<AddressSpace> {
&self.space
}
}
#[derive(Debug)]
pub struct DeviceEntry {
pub(crate) path: String,
pub(crate) class: &'static DeviceClass,
pub(crate) device: Arc<dyn Device>,
pub(crate) instance: Option<Arc<dyn Instance>>,
pub(crate) domain: Option<DomainId>,
pub(crate) space: Option<usize>,
pub(crate) requester: RequesterId,
pub(crate) runnable: Option<RunnableId>,
pub(crate) lazy: Option<LazyId>,
}
impl DeviceEntry {
pub fn path(&self) -> &str {
&self.path
}
pub fn class(&self) -> &'static DeviceClass {
self.class
}
pub fn device(&self) -> &Arc<dyn Device> {
&self.device
}
pub fn instance(&self) -> Option<&Arc<dyn Instance>> {
self.instance.as_ref()
}
pub fn domain(&self) -> Option<DomainId> {
self.domain
}
pub fn space_index(&self) -> Option<usize> {
self.space
}
pub fn requester(&self) -> RequesterId {
self.requester
}
pub fn runnable(&self) -> Option<RunnableId> {
self.runnable
}
pub fn lazy(&self) -> Option<LazyId> {
self.lazy
}
}
#[derive(Debug)]
pub struct Net {
pub(crate) wire: Arc<Wire>,
pub(crate) sources: Vec<PinRef>,
}
impl Net {
pub fn wire(&self) -> &Arc<Wire> {
&self.wire
}
pub fn sources(&self) -> &[PinRef] {
&self.sources
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PinRef {
pub device: usize,
pub port: String,
pub id: WireId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Stepping {
Whole,
Fragment,
}
pub(crate) struct RunAdapter {
inner: Arc<dyn Instance>,
}
impl RunAdapter {
pub(crate) fn new(inner: Arc<dyn Instance>) -> RunAdapter {
RunAdapter { inner }
}
}
impl Runnable for RunAdapter {
fn run(&mut self, budget: Budget) -> Consumed {
self.inner.run(budget)
}
}
pub(crate) struct LazyAdapter {
inner: Arc<dyn Device>,
}
impl LazyAdapter {
pub(crate) fn new(inner: Arc<dyn Device>) -> LazyAdapter {
LazyAdapter { inner }
}
}
impl LazyDevice for LazyAdapter {
fn current_tick(&self) -> u64 {
self.inner.current_tick()
}
fn advance_to(&mut self, tick: u64) {
self.inner.advance_to(tick);
}
fn next_event_tick(&self) -> Option<u64> {
self.inner.next_event_tick()
}
fn sampled_every_cycle(&self) -> bool {
self.inner.sampled_every_cycle()
}
}
#[derive(Debug)]
pub struct Machine {
name: String,
spaces: Vec<SpaceEntry>,
sched: Scheduler,
devices: Vec<DeviceEntry>,
by_path: BTreeMap<String, usize>,
nets: Vec<Net>,
sweep: Vec<PinRef>,
shape: MachineShape,
deferred: Deferred,
recorder: Option<Arc<Recorder>>,
}
#[derive(Debug)]
pub(crate) struct MachineParts {
pub(crate) name: String,
pub(crate) spaces: Vec<(String, Arc<AddressSpace>)>,
pub(crate) sched: Scheduler,
pub(crate) devices: Vec<DeviceEntry>,
pub(crate) nets: Vec<Net>,
pub(crate) sweep: Vec<PinRef>,
pub(crate) shape: MachineShape,
pub(crate) deferred: Deferred,
}
impl Machine {
pub(crate) fn assemble(parts: MachineParts) -> Machine {
let by_path = parts
.devices
.iter()
.enumerate()
.map(|(i, d)| (d.path.clone(), i))
.collect();
Machine {
name: parts.name,
spaces: parts
.spaces
.into_iter()
.map(|(name, space)| SpaceEntry { name, space })
.collect(),
sched: parts.sched,
devices: parts.devices,
by_path,
nets: parts.nets,
sweep: parts.sweep,
shape: parts.shape,
deferred: parts.deferred,
recorder: None,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn spaces(&self) -> &[SpaceEntry] {
&self.spaces
}
pub fn space(&self, name: &str) -> Option<&Arc<AddressSpace>> {
self.spaces
.iter()
.find(|s| s.name == name)
.map(SpaceEntry::space)
}
pub fn devices(&self) -> &[DeviceEntry] {
&self.devices
}
pub fn device(&self, path: &str) -> Option<&DeviceEntry> {
self.by_path.get(path).and_then(|i| self.devices.get(*i))
}
pub fn device_index(&self, path: &str) -> Option<usize> {
self.by_path.get(path).copied()
}
pub fn nets(&self) -> &[Net] {
&self.nets
}
pub fn scheduler(&self) -> &Scheduler {
&self.sched
}
pub fn scheduler_mut(&mut self) -> &mut Scheduler {
&mut self.sched
}
pub fn clocks(&self) -> &ClockForest {
self.sched.forest()
}
pub fn now(&self) -> GlobalTime {
self.sched.now()
}
pub fn shape(&self) -> &MachineShape {
&self.shape
}
pub fn set_host_clock(&mut self, clock: Box<dyn HostClock>) {
self.sched.set_host_clock(clock);
}
pub fn reset(&mut self, kind: ResetKind) {
for i in 0..self.devices.len() {
let device = Arc::clone(&self.devices[i].device);
device.reset(kind);
self.deferred.drain();
}
self.sweep();
}
pub fn sweep(&mut self) {
for pin in &self.sweep {
if let Some(instance) = self.devices[pin.device].instance.as_ref() {
instance.announce(&pin.port);
}
}
self.deferred.drain();
}
pub fn run_quantum(&mut self) -> Result<QuantumReport> {
self.pump_inputs()?;
let report = self.sched.run_quantum_until(GlobalTime::MAX)?;
self.sched.sync_lazy_devices()?;
self.dispatch(&report)?;
Ok(report)
}
pub fn run_until(&mut self, deadline: GlobalTime) -> Result<()> {
self.advance_to(deadline, Stepping::Whole)
}
pub fn step_until(&mut self, deadline: GlobalTime) -> Result<()> {
self.advance_to(deadline, Stepping::Fragment)
}
fn advance_to(&mut self, deadline: GlobalTime, stepping: Stepping) -> Result<()> {
while self.sched.now() < deadline {
let before = self.sched.now();
self.pump_inputs()?;
let report = match stepping {
Stepping::Whole => self.sched.run_quantum_until(deadline)?,
Stepping::Fragment => self.sched.step_quantum_until(deadline)?,
};
self.sched.sync_lazy_devices()?;
self.dispatch(&report)?;
if self.sched.now() <= before {
return Err(Error::Config {
at: self.name.clone(),
message: "virtual time did not advance: the scheduler quantum is zero"
.to_string(),
});
}
}
Ok(())
}
pub fn run_for(&mut self, span: GlobalTime) -> Result<()> {
let deadline = self.sched.now().saturating_add(span);
self.run_until(deadline)
}
pub fn schedule_after_ticks(&mut self, path: &str, ticks: u64, token: u64) -> Result<EventId> {
let index = self.device_index(path).ok_or_else(|| Error::Config {
at: path.to_string(),
message: "no device at this instance path".to_string(),
})?;
let domain = self.devices[index].domain.ok_or_else(|| Error::Config {
at: path.to_string(),
message: "cannot post an event for a device with no clock domain".to_string(),
})?;
let target = EventTarget(u32::try_from(index).unwrap_or(u32::MAX));
Ok(self
.sched
.schedule_after_ticks(domain, ticks, target, token)?)
}
fn dispatch(&mut self, report: &QuantumReport) -> Result<()> {
for event in &report.fired {
let index = event.target.0 as usize;
let Some(instance) = self
.devices
.get(index)
.map(|d| d.instance.clone())
.ok_or_else(|| Error::Config {
at: self.name.clone(),
message: format!(
"event {} is addressed to device {index}, which does not exist",
event.id.seq()
),
})?
else {
continue;
};
instance.event(event.token, &mut self.deferred);
self.deferred.drain();
}
Ok(())
}
pub fn save(&self) -> Result<Vec<u8>> {
let mut w = StateWriter::new(self.shape.clone());
for entry in &self.devices {
let mut chunk = w.chunk(&entry.path, entry.class.name, entry.class.version)?;
entry.device.save(&mut chunk)?;
}
{
let mut chunk = w.chunk(CLOCK_PATH, CLOCK_CLASS, MACHINE_STATE_VERSION)?;
save_clocks(self.sched.forest(), &mut chunk)?;
}
{
let mut chunk = w.chunk(SCHED_PATH, SCHED_CLASS, MACHINE_STATE_VERSION)?;
save_sched(&self.sched, &mut chunk)?;
}
{
let mut chunk = w.chunk(WIRE_PATH, WIRE_CLASS, MACHINE_STATE_VERSION)?;
save_wires(&self.nets, &mut chunk)?;
}
w.to_vec()
}
pub fn load(&mut self, bytes: &[u8]) -> Result<()> {
self.load_with(bytes, &Migrations::new())
}
pub fn load_with(&mut self, bytes: &[u8], migrations: &Migrations) -> Result<()> {
let reader = StateReader::new(bytes)?;
reader.check_shape(&self.shape)?;
for entry in &self.devices {
let chunk = reader.load(
&entry.path,
entry.class.name,
entry.class.version,
migrations,
)?;
let mut r = chunk.reader();
entry.device.load(&mut r)?;
}
let clocks = reader.load(CLOCK_PATH, CLOCK_CLASS, MACHINE_STATE_VERSION, migrations)?;
load_clocks(self.sched.forest_mut(), &mut clocks.reader())?;
let sched = reader.load(SCHED_PATH, SCHED_CLASS, MACHINE_STATE_VERSION, migrations)?;
load_sched(&mut self.sched, &mut sched.reader())?;
let wires = reader.load(WIRE_PATH, WIRE_CLASS, MACHINE_STATE_VERSION, migrations)?;
load_wires(&self.nets, &mut wires.reader())?;
self.deferred.drain();
self.sweep();
if let Some(recorder) = &self.recorder {
recorder.rewind_to(self.sched.now());
}
Ok(())
}
pub fn state_hash(&self) -> Result<u64> {
let mode = self.sched.config().mode;
if !mode.is_deterministic() {
return Err(Error::Config {
at: self.name.clone(),
message: format!(
"a state hash from `{mode}` threading is not reproducible and must not \
become a golden (ROADMAP.md 4.2); run the machine in `deterministic` \
threading, or say `nondeterministic_state_hash` and mean it"
),
});
}
Ok(fnv1a(&self.save()?))
}
pub fn nondeterministic_state_hash(&self) -> Result<u64> {
Ok(fnv1a(&self.save()?))
}
pub fn set_recorder(&mut self, recorder: Arc<Recorder>) -> Result<()> {
let mode = self.sched.config().mode;
if !mode.is_deterministic() {
return Err(Error::Config {
at: self.name.clone(),
message: format!(
"a recording of a `{mode}` run cannot be replayed: the round boundaries \
are reproducible but what happens inside a round is not, so no input log \
can restore it (ROADMAP.md 4.2). Run the machine in `deterministic` \
threading to record it"
),
});
}
recorder.set_shape(self.shape.clone());
self.recorder = Some(recorder);
Ok(())
}
pub fn take_recorder(&mut self) -> Option<Arc<Recorder>> {
self.recorder.take()
}
#[inline]
pub fn recorder(&self) -> Option<&Arc<Recorder>> {
self.recorder.as_ref()
}
fn pump_inputs(&mut self) -> Result<()> {
if let Some(recorder) = &self.recorder {
recorder.deliver(self.sched.now())?;
}
Ok(())
}
#[inline]
pub fn threading_mode(&self) -> crate::core::sched::ThreadingMode {
self.sched.config().mode
}
#[inline]
pub fn safe_point(&self) -> crate::core::sched::SafePoint {
self.sched.safe_point()
}
#[must_use = "the world runs again when the guard is dropped"]
pub fn stop_the_world(&self) -> crate::core::sched::StopGuard {
self.sched.stop_the_world()
}
}
fn fnv1a(bytes: &[u8]) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for b in bytes {
h ^= u64::from(*b);
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
h
}
fn save_clocks(forest: &ClockForest, sink: &mut impl Sink) -> Result<()> {
let oscillators: Vec<_> = forest.oscillators().collect();
sink.write_seq_len(oscillators.len() as u64)?;
for osc in oscillators {
sink.write_u64(forest.unit_position(osc)?)?;
}
let domains: Vec<_> = forest.domains().collect();
sink.write_seq_len(domains.len() as u64)?;
for id in domains {
sink.write_u64(forest.ticks(id)?)?;
}
Ok(())
}
fn load_clocks<'a>(forest: &mut ClockForest, src: &mut impl Source<'a>) -> Result<()> {
let domains: Vec<_> = forest.domains().collect();
let count = src.read_seq_len(8)? as usize;
let oscillators: Vec<_> = forest.oscillators().collect();
if count != oscillators.len() {
return Err(Error::State(format!(
"snapshot has {count} oscillators, this machine has {}",
oscillators.len()
)));
}
for osc in oscillators {
forest.restore_unit_position(osc, src.read_u64()?)?;
}
let count = src.read_seq_len(8)? as usize;
if count != domains.len() {
return Err(Error::State(format!(
"snapshot has {count} clock domains, this machine has {}",
domains.len()
)));
}
for id in domains {
forest.restore_ticks(id, src.read_u64()?)?;
}
Ok(())
}
fn save_sched(sched: &Scheduler, sink: &mut impl Sink) -> Result<()> {
let snapshot = sched.snapshot();
sink.write_u128(snapshot.now.raw())?;
sink.write_u64(snapshot.next_seq)?;
sink.write_u64(snapshot.cursor as u64)?;
sink.write_seq_len(snapshot.events.len() as u64)?;
for event in &snapshot.events {
sink.write_u128(event.time.raw())?;
sink.write_u64(event.id.seq())?;
sink.write_u32(event.target.0)?;
sink.write_u64(event.token)?;
}
Ok(())
}
fn load_sched<'a>(sched: &mut Scheduler, src: &mut impl Source<'a>) -> Result<()> {
let now = GlobalTime::from_raw(src.read_u128()?);
let next_seq = src.read_u64()?;
let cursor = usize::try_from(src.read_u64()?)
.map_err(|_| Error::State(String::from("scheduler cursor does not fit this host")))?;
let count = src.read_seq_len(36)? as usize;
let mut events = Vec::with_capacity(count.min(src.remaining()));
for _ in 0..count {
events.push(Event {
time: GlobalTime::from_raw(src.read_u128()?),
id: EventId::from_seq(src.read_u64()?),
target: EventTarget(src.read_u32()?),
token: src.read_u64()?,
});
}
sched.restore(&SchedulerSnapshot {
now,
next_seq,
cursor,
events,
})?;
Ok(())
}
fn save_wires(nets: &[Net], sink: &mut impl Sink) -> Result<()> {
sink.write_seq_len(nets.len() as u64)?;
for net in nets {
let levels = net.wire.snapshot();
sink.write_seq_len(levels.len() as u64)?;
for (id, level) in levels {
sink.write_u64(id.raw())?;
sink.write_u8(u8::from(level.is_high()))?;
}
}
Ok(())
}
fn load_wires<'a>(nets: &[Net], src: &mut impl Source<'a>) -> Result<()> {
let count = src.read_seq_len(8)? as usize;
if count != nets.len() {
return Err(Error::State(format!(
"snapshot has {count} wire nets, this machine has {}",
nets.len()
)));
}
for net in nets {
let sources = src.read_seq_len(9)? as usize;
let mut levels = Vec::with_capacity(sources.min(src.remaining()));
for _ in 0..sources {
let id = WireId::new(src.read_u64()?);
let level = match src.read_u8()? {
0 => Level::Low,
1 => Level::High,
other => {
return Err(Error::State(format!("wire level {other} is not 0 or 1")));
}
};
levels.push((id, level));
}
net.wire.restore(&levels);
}
for net in nets {
net.wire.refresh();
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_machine_chunk_paths_cannot_collide_with_a_device() {
assert!(CLOCK_PATH.starts_with('/'));
assert!(WIRE_PATH.starts_with('/'));
}
#[test]
fn the_state_hash_is_a_function_of_the_bytes() {
assert_eq!(fnv1a(b"abc"), fnv1a(b"abc"));
assert_ne!(fnv1a(b"abc"), fnv1a(b"abd"));
assert_eq!(fnv1a(b""), 0xcbf2_9ce4_8422_2325);
}
}