1use alloc::{
13 string::{String, ToString},
14 sync::Arc,
15 vec::Vec,
16};
17
18use miden_core::{
19 mast::MastForest,
20 program::StackInputs,
21 serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
22};
23use miden_debug_types::Uri;
24use miden_mast_package::{Package, debug_info::PackageDebugInfo};
25use miden_processor::{
26 ExecutionOptions, LoadedMastForest,
27 advice::{AdviceInputs, AdviceMutation},
28};
29use miden_utils_sync::RwLock;
30
31use super::advice::{read_event_log, write_event_log};
32
33#[derive(Clone, Default)]
40pub struct MastForestRecorder {
41 forests: Arc<RwLock<Vec<LoadedMastForest>>>,
42}
43
44impl MastForestRecorder {
45 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn snapshot(&self) -> Vec<LoadedMastForest> {
52 self.forests.read().clone()
53 }
54
55 #[cfg(feature = "dap")]
57 pub(crate) fn record(&self, forest: LoadedMastForest) {
58 let mut guard = self.forests.write();
59 if !guard
60 .iter()
61 .any(|existing| Arc::ptr_eq(existing.mast_forest(), forest.mast_forest()))
62 {
63 guard.push(forest);
64 }
65 }
66
67 #[cfg(feature = "dap")]
69 pub(crate) fn clear(&self) {
70 self.forests.write().clear();
71 }
72}
73
74#[derive(Clone, Debug, PartialEq, Eq)]
76pub struct ReplaySnapshotWrite {
77 pub path: Uri,
78 pub event_count: usize,
79 pub forest_count: usize,
80}
81
82#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
84#[error("failed to write replay snapshot to {}: {}", path, message)]
85pub struct ReplaySnapshotWriteError {
86 pub path: Uri,
87 pub message: String,
88}
89
90#[derive(Clone, Debug, Default)]
92pub struct ReplaySnapshotRecorder {
93 status: Arc<RwLock<Option<Result<ReplaySnapshotWrite, ReplaySnapshotWriteError>>>>,
94}
95
96impl ReplaySnapshotRecorder {
97 pub fn new() -> Self {
98 Self::default()
99 }
100
101 pub fn take(&self) -> Option<Result<ReplaySnapshotWrite, ReplaySnapshotWriteError>> {
103 self.status.write().take()
104 }
105
106 #[cfg(feature = "dap")]
107 pub(crate) fn record_success(&self, write: ReplaySnapshotWrite) {
108 *self.status.write() = Some(Ok(write));
109 }
110
111 #[cfg(feature = "dap")]
112 pub(crate) fn record_error(&self, path: Uri, err: impl ToString) {
113 *self.status.write() = Some(Err(ReplaySnapshotWriteError {
114 path,
115 message: err.to_string(),
116 }));
117 }
118}
119
120const SNAPSHOT_MAGIC: [u8; 6] = *b"MDNSNP";
123const SNAPSHOT_VERSION: u8 = 3;
124
125pub struct ReplaySnapshot {
127 pub package: Arc<Package>,
129 pub stack_inputs: StackInputs,
131 pub advice_inputs: AdviceInputs,
133 pub options: ExecutionOptions,
135 pub mast_forests: Vec<LoadedMastForest>,
138 pub event_log: Vec<Vec<AdviceMutation>>,
141}
142
143impl ReplaySnapshot {
144 #[cfg(feature = "std")]
146 pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
147 std::fs::write(path, self.to_bytes())
148 }
149
150 #[cfg(feature = "std")]
152 pub fn read_from_file(path: impl AsRef<std::path::Path>) -> Result<Self, ReplaySnapshotError> {
153 let bytes = std::fs::read(path).map_err(ReplaySnapshotError::Io)?;
154 Self::read_from_bytes(&bytes).map_err(ReplaySnapshotError::Deserialization)
155 }
156
157 pub fn to_bytes(&self) -> Vec<u8> {
159 let mut bytes = Vec::new();
160 self.write_into(&mut bytes);
161 bytes
162 }
163
164 pub fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
166 let mut reader = miden_core::serde::SliceReader::new(bytes);
167 Self::read_from(&mut reader)
168 }
169}
170
171impl Serializable for ReplaySnapshot {
172 fn write_into<W: ByteWriter>(&self, target: &mut W) {
173 target.write_bytes(&SNAPSHOT_MAGIC);
174 target.write_u8(SNAPSHOT_VERSION);
175 self.package.write_into(target);
176 self.stack_inputs.write_into(target);
177 self.advice_inputs.write_into(target);
178 write_execution_options(&self.options, target);
179 target.write_usize(self.mast_forests.len());
180 for forest in &self.mast_forests {
181 forest.mast_forest().as_ref().write_into(target);
182 match forest.package_debug_info().ok().flatten() {
183 Some(debug_info) => {
184 target.write_bool(true);
185 debug_info.as_ref().write_into(target);
186 }
187 None => {
188 target.write_bool(false);
189 }
190 }
191 }
192 write_event_log(&self.event_log, target);
193 }
194}
195
196impl Deserializable for ReplaySnapshot {
197 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
198 let magic: [u8; 6] = source.read_array()?;
199 if magic != SNAPSHOT_MAGIC {
200 return Err(DeserializationError::InvalidValue(
201 "not a Miden debugger replay snapshot (bad magic)".to_string(),
202 ));
203 }
204 let version = source.read_u8()?;
205 if version != SNAPSHOT_VERSION {
206 return Err(DeserializationError::InvalidValue(format!(
207 "unsupported replay snapshot version {version} (expected {SNAPSHOT_VERSION})"
208 )));
209 }
210 let package = Arc::new(Package::read_from_trusted(source)?);
211 let stack_inputs = StackInputs::read_from(source)?;
212 let advice_inputs = AdviceInputs::read_from(source)?;
213 let options = read_execution_options(source)?;
214 let forest_count = source.read_usize()?;
215 let mut mast_forests = Vec::with_capacity(forest_count);
216 for _ in 0..forest_count {
217 let mast_forest = Arc::new(MastForest::read_from(source)?);
218 mast_forests.push(if source.read_bool()? {
219 let debug_info = Some(PackageDebugInfo::read_from(source)?);
220 LoadedMastForest::with_package_debug_info(mast_forest, Ok(debug_info))
221 } else {
222 LoadedMastForest::new(mast_forest)
223 });
224 }
225 let event_log = read_event_log(source)?;
226 Ok(Self {
227 package,
228 stack_inputs,
229 advice_inputs,
230 options,
231 mast_forests,
232 event_log,
233 })
234 }
235}
236
237fn write_execution_options<W: ByteWriter>(options: &ExecutionOptions, target: &mut W) {
238 target.write_u32(options.max_cycles());
239 target.write_u32(options.expected_cycles());
240 target.write_usize(options.core_trace_fragment_size());
241 target.write_usize(options.max_advice_size_bytes());
242 target.write_usize(options.max_hash_len_bytes());
243 target.write_bool(options.overlapped_trace_build());
244 target.write_usize(options.max_num_continuations());
245 target.write_usize(options.max_stack_depth());
246 target.write_usize(options.max_memory_elements());
247}
248
249fn read_execution_options<R: ByteReader>(
250 source: &mut R,
251) -> Result<ExecutionOptions, DeserializationError> {
252 let max_cycles = source.read_u32()?;
253 let expected_cycles = source.read_u32()?;
254 let core_trace_fragment_size = source.read_usize()?;
255 let max_advice_size_bytes = source.read_usize()?;
256 let max_hash_len_bytes = source.read_usize()?;
257 let overlapped_trace_build = source.read_bool()?;
258 let max_num_continuations = source.read_usize()?;
259 let max_stack_depth = source.read_usize()?;
260 let max_memory_elements = source.read_usize()?;
261
262 ExecutionOptions::new(Some(max_cycles), expected_cycles, core_trace_fragment_size)
263 .map_err(|err| {
264 DeserializationError::InvalidValue(format!("invalid execution options: {err}"))
265 })
266 .and_then(|options| {
267 options
268 .with_max_advice_size_bytes(max_advice_size_bytes)
269 .with_max_hash_len_bytes(max_hash_len_bytes)
270 .with_overlapped_trace_build(overlapped_trace_build)
271 .with_max_num_continuations(max_num_continuations)
272 .with_max_memory_elements(max_memory_elements)
273 .with_max_stack_depth(max_stack_depth)
274 .map_err(|err| {
275 DeserializationError::InvalidValue(format!("invalid execution options: {err}"))
276 })
277 })
278}
279
280#[derive(Debug, thiserror::Error)]
282#[cfg(feature = "std")]
283pub enum ReplaySnapshotError {
284 #[error("failed to read replay snapshot file: {0}")]
285 Io(std::io::Error),
286 #[error("failed to deserialize replay snapshot: {0}")]
287 Deserialization(DeserializationError),
288}
289
290#[cfg(test)]
291mod tests {
292 use miden_assembly::{Assembler, DefaultSourceManager};
293 use miden_core::{Felt, Word, crypto::merkle::InnerNodeInfo};
294
295 use super::*;
296
297 fn word(values: [u32; 4]) -> Word {
298 Word::from(values.map(Felt::from))
299 }
300
301 #[test]
304 fn replay_snapshot_round_trips() {
305 let source_manager = Arc::new(DefaultSourceManager::default());
306 let program = Assembler::new(source_manager)
307 .assemble_program("program", "begin push.1 push.2 add drop end")
308 .map(Arc::<Package>::from)
309 .expect("failed to assemble test program");
310 let forest = LoadedMastForest::with_package_debug_info(
311 program.mast_forest().clone(),
312 program.debug_info(),
313 );
314
315 let event_log = vec![
316 vec![AdviceMutation::extend_advice_stack(
317 [Felt::from(7u32), Felt::from(8u32)].into_iter().collect(),
318 )],
319 vec![],
320 vec![AdviceMutation::extend_merkle_store([InnerNodeInfo {
321 value: word([1, 2, 3, 4]),
322 left: word([5, 6, 7, 8]),
323 right: word([9, 10, 11, 12]),
324 }])],
325 ];
326
327 let snapshot = ReplaySnapshot {
328 package: program.clone(),
329 stack_inputs: StackInputs::new(&[Felt::from(42u32), Felt::from(43u32)]).unwrap(),
330 advice_inputs: AdviceInputs::default()
331 .with_stack([Felt::from(99u32)].into_iter().collect()),
332 options: ExecutionOptions::new(Some(100_000), 32, 1024)
333 .unwrap()
334 .with_max_advice_size_bytes(256)
335 .with_max_hash_len_bytes(512)
336 .with_overlapped_trace_build(false)
337 .with_max_num_continuations(128)
338 .with_max_memory_elements(1024)
339 .with_max_stack_depth(128)
340 .unwrap(),
341 mast_forests: vec![forest],
342 event_log,
343 };
344
345 let restored = ReplaySnapshot::read_from_bytes(&snapshot.to_bytes())
346 .expect("snapshot failed to deserialize");
347
348 assert_eq!(restored.package.commitment(), snapshot.package.commitment());
349 assert_eq!(restored.stack_inputs, snapshot.stack_inputs);
350 assert_eq!(restored.advice_inputs, snapshot.advice_inputs);
351 assert_eq!(restored.options, snapshot.options);
352 assert_eq!(restored.mast_forests.len(), 1);
353 assert_eq!(restored.event_log.len(), 3);
354 assert_eq!(restored.event_log[1].len(), 0, "empty event batch must survive");
355 match restored.event_log[0].as_slice() {
356 [AdviceMutation::ExtendStack { stack }] => {
357 assert_eq!(
358 stack.iter().copied().collect::<Vec<_>>(),
359 [Felt::from(7u32), Felt::from(8u32)],
360 );
361 }
362 _ => panic!("unexpected first event batch"),
363 }
364 match restored.event_log[2].as_slice() {
365 [AdviceMutation::ExtendMerkleStore { inner_nodes }] => {
366 assert_eq!(inner_nodes.len(), 1);
367 assert_eq!(inner_nodes[0].value, word([1, 2, 3, 4]));
368 assert_eq!(inner_nodes[0].right, word([9, 10, 11, 12]));
369 }
370 _ => panic!("unexpected merkle-store event batch"),
371 }
372 }
373
374 #[test]
376 fn replay_snapshot_rejects_bad_magic() {
377 let err = ReplaySnapshot::read_from_bytes(b"not a snapshot at all really");
378 assert!(err.is_err(), "expected deserialization to fail on bad magic");
379 }
380
381 #[test]
384 fn replay_snapshot_rejects_previous_version() {
385 let mut bytes = SNAPSHOT_MAGIC.to_vec();
386 bytes.push(SNAPSHOT_VERSION - 1);
387
388 let Err(err) = ReplaySnapshot::read_from_bytes(&bytes) else {
389 panic!("expected deserialization to reject a snapshot from a previous version");
390 };
391 assert!(err.to_string().contains("unsupported replay snapshot version"));
392 }
393}