use alloc::{
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use miden_core::{
mast::MastForest,
program::StackInputs,
serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
};
use miden_debug_types::Uri;
use miden_mast_package::{Package, debug_info::PackageDebugInfo};
use miden_processor::{
ExecutionOptions, LoadedMastForest,
advice::{AdviceInputs, AdviceMutation},
};
use miden_utils_sync::RwLock;
use super::advice::{read_event_log, write_event_log};
#[derive(Clone, Default)]
pub struct MastForestRecorder {
forests: Arc<RwLock<Vec<LoadedMastForest>>>,
}
impl MastForestRecorder {
pub fn new() -> Self {
Self::default()
}
pub fn snapshot(&self) -> Vec<LoadedMastForest> {
self.forests.read().clone()
}
#[cfg(feature = "dap")]
pub(crate) fn record(&self, forest: LoadedMastForest) {
let mut guard = self.forests.write();
if !guard
.iter()
.any(|existing| Arc::ptr_eq(existing.mast_forest(), forest.mast_forest()))
{
guard.push(forest);
}
}
#[cfg(feature = "dap")]
pub(crate) fn clear(&self) {
self.forests.write().clear();
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReplaySnapshotWrite {
pub path: Uri,
pub event_count: usize,
pub forest_count: usize,
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[error("failed to write replay snapshot to {}: {}", path, message)]
pub struct ReplaySnapshotWriteError {
pub path: Uri,
pub message: String,
}
#[derive(Clone, Debug, Default)]
pub struct ReplaySnapshotRecorder {
status: Arc<RwLock<Option<Result<ReplaySnapshotWrite, ReplaySnapshotWriteError>>>>,
}
impl ReplaySnapshotRecorder {
pub fn new() -> Self {
Self::default()
}
pub fn take(&self) -> Option<Result<ReplaySnapshotWrite, ReplaySnapshotWriteError>> {
self.status.write().take()
}
#[cfg(feature = "dap")]
pub(crate) fn record_success(&self, write: ReplaySnapshotWrite) {
*self.status.write() = Some(Ok(write));
}
#[cfg(feature = "dap")]
pub(crate) fn record_error(&self, path: Uri, err: impl ToString) {
*self.status.write() = Some(Err(ReplaySnapshotWriteError {
path,
message: err.to_string(),
}));
}
}
const SNAPSHOT_MAGIC: [u8; 6] = *b"MDNSNP";
const SNAPSHOT_VERSION: u8 = 3;
pub struct ReplaySnapshot {
pub package: Arc<Package>,
pub stack_inputs: StackInputs,
pub advice_inputs: AdviceInputs,
pub options: ExecutionOptions,
pub mast_forests: Vec<LoadedMastForest>,
pub event_log: Vec<Vec<AdviceMutation>>,
}
impl ReplaySnapshot {
#[cfg(feature = "std")]
pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
std::fs::write(path, self.to_bytes())
}
#[cfg(feature = "std")]
pub fn read_from_file(path: impl AsRef<std::path::Path>) -> Result<Self, ReplaySnapshotError> {
let bytes = std::fs::read(path).map_err(ReplaySnapshotError::Io)?;
Self::read_from_bytes(&bytes).map_err(ReplaySnapshotError::Deserialization)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
self.write_into(&mut bytes);
bytes
}
pub fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
let mut reader = miden_core::serde::SliceReader::new(bytes);
Self::read_from(&mut reader)
}
}
impl Serializable for ReplaySnapshot {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_bytes(&SNAPSHOT_MAGIC);
target.write_u8(SNAPSHOT_VERSION);
self.package.write_into(target);
self.stack_inputs.write_into(target);
self.advice_inputs.write_into(target);
write_execution_options(&self.options, target);
target.write_usize(self.mast_forests.len());
for forest in &self.mast_forests {
forest.mast_forest().as_ref().write_into(target);
match forest.package_debug_info().ok().flatten() {
Some(debug_info) => {
target.write_bool(true);
debug_info.as_ref().write_into(target);
}
None => {
target.write_bool(false);
}
}
}
write_event_log(&self.event_log, target);
}
}
impl Deserializable for ReplaySnapshot {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let magic: [u8; 6] = source.read_array()?;
if magic != SNAPSHOT_MAGIC {
return Err(DeserializationError::InvalidValue(
"not a Miden debugger replay snapshot (bad magic)".to_string(),
));
}
let version = source.read_u8()?;
if version != SNAPSHOT_VERSION {
return Err(DeserializationError::InvalidValue(format!(
"unsupported replay snapshot version {version} (expected {SNAPSHOT_VERSION})"
)));
}
let package = Arc::new(Package::read_from_trusted(source)?);
let stack_inputs = StackInputs::read_from(source)?;
let advice_inputs = AdviceInputs::read_from(source)?;
let options = read_execution_options(source)?;
let forest_count = source.read_usize()?;
let mut mast_forests = Vec::with_capacity(forest_count);
for _ in 0..forest_count {
let mast_forest = Arc::new(MastForest::read_from(source)?);
mast_forests.push(if source.read_bool()? {
let debug_info = Some(PackageDebugInfo::read_from(source)?);
LoadedMastForest::with_package_debug_info(mast_forest, Ok(debug_info))
} else {
LoadedMastForest::new(mast_forest)
});
}
let event_log = read_event_log(source)?;
Ok(Self {
package,
stack_inputs,
advice_inputs,
options,
mast_forests,
event_log,
})
}
}
fn write_execution_options<W: ByteWriter>(options: &ExecutionOptions, target: &mut W) {
target.write_u32(options.max_cycles());
target.write_u32(options.expected_cycles());
target.write_usize(options.core_trace_fragment_size());
target.write_usize(options.max_advice_size_bytes());
target.write_usize(options.max_hash_len_bytes());
target.write_bool(options.overlapped_trace_build());
target.write_usize(options.max_num_continuations());
target.write_usize(options.max_stack_depth());
target.write_usize(options.max_memory_elements());
}
fn read_execution_options<R: ByteReader>(
source: &mut R,
) -> Result<ExecutionOptions, DeserializationError> {
let max_cycles = source.read_u32()?;
let expected_cycles = source.read_u32()?;
let core_trace_fragment_size = source.read_usize()?;
let max_advice_size_bytes = source.read_usize()?;
let max_hash_len_bytes = source.read_usize()?;
let overlapped_trace_build = source.read_bool()?;
let max_num_continuations = source.read_usize()?;
let max_stack_depth = source.read_usize()?;
let max_memory_elements = source.read_usize()?;
ExecutionOptions::new(Some(max_cycles), expected_cycles, core_trace_fragment_size)
.map_err(|err| {
DeserializationError::InvalidValue(format!("invalid execution options: {err}"))
})
.and_then(|options| {
options
.with_max_advice_size_bytes(max_advice_size_bytes)
.with_max_hash_len_bytes(max_hash_len_bytes)
.with_overlapped_trace_build(overlapped_trace_build)
.with_max_num_continuations(max_num_continuations)
.with_max_memory_elements(max_memory_elements)
.with_max_stack_depth(max_stack_depth)
.map_err(|err| {
DeserializationError::InvalidValue(format!("invalid execution options: {err}"))
})
})
}
#[derive(Debug, thiserror::Error)]
#[cfg(feature = "std")]
pub enum ReplaySnapshotError {
#[error("failed to read replay snapshot file: {0}")]
Io(std::io::Error),
#[error("failed to deserialize replay snapshot: {0}")]
Deserialization(DeserializationError),
}
#[cfg(test)]
mod tests;