use celox_state_layout::STATE_HEADER_SIZE;
use num_bigint::BigUint;
use super::host::Simulator;
use crate::backend::SimBackend;
#[derive(Clone)]
pub struct StateImage {
fingerprint: u64,
bytes: Box<[u8]>,
}
impl StateImage {
pub fn capture<B: SimBackend>(backend: &B, fingerprint: u64) -> Self {
let range = state_range(backend);
let (ptr, len) = backend.memory_as_ptr();
assert!(range.end <= len, "stable region exceeds the memory image");
let memory = unsafe { std::slice::from_raw_parts(ptr, len) };
Self {
fingerprint,
bytes: memory[range].into(),
}
}
pub fn matches(&self, fingerprint: u64) -> bool {
self.fingerprint == fingerprint
}
pub fn restore_into<B: SimBackend>(
&self,
backend: &mut B,
fingerprint: u64,
) -> Result<(), CheckpointError> {
if !self.matches(fingerprint) {
return Err(CheckpointError::DesignMismatch);
}
let range = state_range(backend);
assert_eq!(range.len(), self.bytes.len());
backend.write_memory(range.start, &self.bytes);
Ok(())
}
pub fn len(&self) -> usize {
self.bytes.len()
}
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
}
impl std::fmt::Debug for StateImage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StateImage")
.field("len", &self.bytes.len())
.finish_non_exhaustive()
}
}
fn state_range<B: SimBackend>(backend: &B) -> std::ops::Range<usize> {
STATE_HEADER_SIZE..backend.layout().total_size
}
#[derive(Clone)]
pub struct Checkpoint {
image: StateImage,
comb_observer_snapshots: Vec<Vec<(BigUint, BigUint)>>,
comb_observer_initial_eval: bool,
}
impl Checkpoint {
pub fn state_size(&self) -> usize {
self.image.len()
}
}
impl std::fmt::Debug for Checkpoint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Checkpoint")
.field("state_size", &self.state_size())
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum CheckpointError {
#[error(
"checkpoints are not supported for designs with external components, whose state Celox cannot capture"
)]
ExternalComponents,
#[error("the checkpoint was taken from a different design")]
DesignMismatch,
#[error(
"returning to time {time} would rewind the VCD output, which reached time {last_dumped}; \
switch to a new VCD file first"
)]
VcdRewind { time: u64, last_dumped: u64 },
}
impl<B: SimBackend> Simulator<B> {
pub fn checkpoint(&self) -> Result<Checkpoint, CheckpointError> {
if !self.components.is_empty() {
return Err(CheckpointError::ExternalComponents);
}
Ok(Checkpoint {
image: StateImage::capture(&self.backend, self.state_fingerprint()),
comb_observer_snapshots: self.comb_observer_snapshots.clone(),
comb_observer_initial_eval: self.comb_observer_initial_eval,
})
}
pub fn restore(&mut self, checkpoint: &Checkpoint) -> Result<(), CheckpointError> {
self.validate_restore(checkpoint)?;
let fingerprint = self.state_fingerprint();
checkpoint
.image
.restore_into(&mut self.backend, fingerprint)?;
self.comb_observer_snapshots
.clone_from(&checkpoint.comb_observer_snapshots);
self.comb_observer_initial_eval = checkpoint.comb_observer_initial_eval;
if let Some(writer) = &mut self.vcd_writer {
writer.rescan();
}
self.dirty = true;
self.settle_dirty_for_runtime_event_drain();
Ok(())
}
pub(crate) fn validate_restore(&self, checkpoint: &Checkpoint) -> Result<(), CheckpointError> {
if !self.components.is_empty() {
return Err(CheckpointError::ExternalComponents);
}
if !checkpoint.image.matches(self.state_fingerprint()) {
return Err(CheckpointError::DesignMismatch);
}
Ok(())
}
pub fn state_fingerprint(&self) -> u64 {
*self
.checkpoint_fingerprint
.get_or_init(|| crate::ir::state_fingerprint(self.backend.layout(), &self.program))
}
}