Skip to main content

miden_debug_engine/exec/
executor.rs

1use std::{
2    cell::{Cell, RefCell},
3    collections::{BTreeMap, VecDeque},
4    fmt,
5    ops::Deref,
6    rc::Rc,
7    sync::{Arc, Mutex},
8};
9
10use log::Level;
11use miden_assembly_syntax::diagnostics::Report;
12use miden_core::{operations::DebugVarInfo, program::StackInputs};
13use miden_debug_types::{SourceManager, SourceManagerExt};
14use miden_mast_package::Package;
15use miden_package_registry::PackageCache;
16use miden_processor::{
17    ContextId, ExecutionError, ExecutionOptions, FastProcessor, Felt, LoadedMastForest,
18    ProcessorState,
19    advice::{AdviceInputs, AdviceMutation},
20    event::{EventError, EventHandler, EventName},
21    trace::RowIndex,
22};
23
24use super::{
25    DebugExecutor, DebuggerHost, Event, ExecutionConfig, ExecutionTrace,
26    event::{FRAME_END_EVENT, FRAME_START_EVENT, PRINTLN_EVENT},
27    query::read_memory_bytes,
28};
29use crate::{
30    HybridPackageRegistry,
31    debug::{CallStack, DebugVarTracker, NativePtr},
32    felt::FromMidenRepr,
33    profiling::{Profiler, ProfilerConfig},
34};
35
36/// Maximum number of bytes for a single `println` output.
37///
38/// A limit is required as `u32::MAX` exceeds the size that strings can take in Miden VM. The limit
39/// is generous and still permits use cases like formatting a large amount of data in storage.
40///
41/// Exceeding the limit likely indicates a bug in the corresponding trace event handling.
42const MAX_PRINTLN_BYTES: usize = 512 * 1024;
43
44/// The [Executor] is responsible for executing a program with the Miden VM.
45///
46/// It is used by either converting it into a [DebugExecutor], and using that to
47/// manage execution step-by-step, such as is done by the debugger; or by running
48/// the program to completion and obtaining an [ExecutionTrace], which can be used
49/// to introspect the final program state.
50pub struct Executor {
51    stack: StackInputs,
52    advice: AdviceInputs,
53    options: ExecutionOptions,
54    event_handlers: Vec<(EventName, Arc<dyn EventHandler>)>,
55    registry: HybridPackageRegistry,
56    record_event_mutations: bool,
57    profiler_config: ProfilerConfig,
58}
59
60impl Executor {
61    /// Construct an executor with the given arguments on the operand stack
62    pub fn new(args: Vec<Felt>) -> Self {
63        let config = ExecutionConfig {
64            inputs: StackInputs::new(&args).expect("invalid stack inputs"),
65            ..Default::default()
66        };
67
68        Self::from_config(config)
69    }
70
71    /// Construct an executor from the given configuration
72    ///
73    /// NOTE: The execution options for tracing/debugging will be set to true for you
74    pub fn from_config(config: ExecutionConfig) -> Self {
75        let ExecutionConfig {
76            inputs,
77            advice_inputs,
78            options,
79        } = config;
80
81        Self {
82            stack: inputs,
83            advice: advice_inputs,
84            options,
85            event_handlers: Default::default(),
86            registry: HybridPackageRegistry::empty(),
87            record_event_mutations: false,
88            profiler_config: Default::default(),
89        }
90    }
91
92    #[inline]
93    pub fn with_registry(mut self, registry: HybridPackageRegistry) -> Self {
94        self.registry = registry;
95        self
96    }
97
98    /// Set the contents of memory for the shadow stack frame of the entrypoint
99    pub fn with_advice_inputs(&mut self, advice: AdviceInputs) -> &mut Self {
100        self.advice.extend(advice);
101        self
102    }
103
104    /// Add a [Package] to the execution context
105    pub fn with_package(&mut self, package: Arc<Package>) -> Result<&mut Self, Report> {
106        self.registry.cache_package(package)?;
107        Ok(self)
108    }
109
110    /// Record the advice mutations produced by each event handler invocation during execution.
111    ///
112    /// Recording is a private detail of the debug host created by [Executor::into_debug]: once
113    /// the program completes, take the log via [DebuggerHost::take_recorded_event_mutations] on
114    /// the [DebugExecutor]'s host, and feed it back into [Executor::into_debug_with_replay] to
115    /// debug the same execution later without the original event handlers (e.g. transaction
116    /// debugging with event replay).
117    ///
118    /// Mutations are only recorded for live event handling; nothing is recorded while an event
119    /// replay queue is being consumed.
120    pub fn with_event_advice_mutations_recording(&mut self) -> &mut Self {
121        self.record_event_mutations = true;
122        self
123    }
124
125    /// Register a VM event handler to be available during execution.
126    pub fn register_event_handler(
127        &mut self,
128        event: EventName,
129        handler: Arc<dyn EventHandler>,
130    ) -> Result<&mut Self, ExecutionError> {
131        self.event_handlers.push((event, handler));
132        Ok(self)
133    }
134
135    /// Set the profiler configuration for this executor.
136    pub fn with_profiler_config(&mut self, profiler_config: ProfilerConfig) -> &mut Self {
137        self.profiler_config = profiler_config;
138        self
139    }
140
141    /// Convert this [Executor] into a [DebugExecutor], which captures much more information
142    /// about the program being executed, and must be stepped manually.
143    pub fn into_debug(
144        mut self,
145        package: Arc<Package>,
146        source_manager: Arc<dyn SourceManager>,
147    ) -> DebugExecutor {
148        assert!(package.is_program());
149
150        log::debug!("creating debug executor");
151
152        let mut host = DebuggerHost::new(source_manager.clone());
153        for lib in self.registry.all() {
154            host.load_package(lib);
155        }
156        for (event, handler) in core::mem::take(&mut self.event_handlers) {
157            host.register_event_handler(event, handler)
158                .expect("failed to register debug executor event handler");
159        }
160        if self.record_event_mutations {
161            host = host.with_event_advice_mutations_recording();
162        }
163
164        let events: Arc<Mutex<BTreeMap<RowIndex, Event>>> = Arc::new(Default::default());
165        register_builtin_event_handlers(&mut host, Arc::clone(&events));
166
167        // Set up debug variable tracking
168        let debug_var_events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>> =
169            Rc::new(Default::default());
170
171        let mut processor = FastProcessor::new_with_options(self.stack, self.advice, self.options)
172            .expect("advice inputs should fit advice map limits");
173
174        let root_context = ContextId::root();
175        let resume_ctx = processor
176            .get_initial_resume_context_for_package(package)
177            .expect("failed to get initial resume context");
178
179        let callstack = CallStack::new(events);
180        let debug_vars = DebugVarTracker::new(debug_var_events);
181        DebugExecutor {
182            processor,
183            host,
184            resume_ctx: Some(resume_ctx),
185            current_stack: vec![],
186            current_op: None,
187            current_asmop: None,
188            stack_outputs: Default::default(),
189            contexts: Default::default(),
190            root_context,
191            current_context: root_context,
192            callstack,
193            current_proc: None,
194            debug_vars,
195            last_debug_var_count: 0,
196            recent: VecDeque::with_capacity(5),
197            cycle: 0,
198            stopped: false,
199            profiler: Profiler::from_config(self.profiler_config),
200        }
201    }
202
203    /// Convert this [Executor] into a [DebugExecutor] with event replay support.
204    ///
205    /// Like [`into_debug`](Self::into_debug), but additionally:
206    /// - Loads `extra_forests` into the host's MAST forest store
207    /// - Sets the event replay queue so that `on_event()` returns pre-recorded mutations
208    ///
209    /// This is used for transaction debugging where events were recorded during a prior
210    /// execution with the real transaction host.
211    pub fn into_debug_with_replay(
212        self,
213        package: Arc<Package>,
214        source_manager: Arc<dyn SourceManager>,
215        extra_mast_forests: Vec<LoadedMastForest>,
216        event_replay: VecDeque<Vec<AdviceMutation>>,
217    ) -> DebugExecutor {
218        assert!(package.is_program());
219
220        log::debug!("creating debug executor with event replay");
221
222        let mut host = DebuggerHost::new(source_manager.clone());
223        for lib in self.registry.all() {
224            host.load_package(lib);
225        }
226        for forest in extra_mast_forests {
227            host.load_mast_forest(forest);
228        }
229        host.set_event_replay(event_replay);
230
231        let debug_var_events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>> =
232            Rc::new(Default::default());
233
234        let events: Arc<Mutex<BTreeMap<RowIndex, Event>>> = Arc::new(Default::default());
235        register_builtin_event_handlers(&mut host, Arc::clone(&events));
236
237        let mut processor = FastProcessor::new_with_options(self.stack, self.advice, self.options)
238            .expect("advice inputs should fit advice map limits");
239
240        let root_context = ContextId::root();
241        let resume_ctx = processor
242            .get_initial_resume_context_for_package(package)
243            .expect("failed to get initial resume context");
244
245        let callstack = CallStack::new(events);
246        let debug_vars = DebugVarTracker::new(debug_var_events);
247        DebugExecutor {
248            processor,
249            host,
250            resume_ctx: Some(resume_ctx),
251            current_stack: vec![],
252            current_op: None,
253            current_asmop: None,
254            stack_outputs: Default::default(),
255            contexts: Default::default(),
256            root_context,
257            current_context: root_context,
258            callstack,
259            current_proc: None,
260            debug_vars,
261            last_debug_var_count: 0,
262            recent: VecDeque::with_capacity(5),
263            cycle: 0,
264            stopped: false,
265            profiler: Profiler::from_config(self.profiler_config),
266        }
267    }
268
269    /// Execute the given program until termination, producing a trace
270    pub fn capture_trace(
271        self,
272        package: Arc<Package>,
273        source_manager: Arc<dyn SourceManager>,
274    ) -> ExecutionTrace {
275        let mut executor = self.into_debug(package, source_manager);
276        loop {
277            if executor.stopped {
278                break;
279            }
280            match executor.step() {
281                Ok(_) => continue,
282                Err(err) => {
283                    log::warn!(
284                        target: "executor",
285                        "capture_trace stopped early at cycle {}: {err}",
286                        executor.cycle,
287                    );
288                    break;
289                }
290            }
291        }
292        executor.into_execution_trace()
293    }
294
295    /// Execute the given program, producing a trace
296    #[track_caller]
297    pub fn execute(
298        self,
299        package: Arc<Package>,
300        source_manager: Arc<dyn SourceManager>,
301    ) -> ExecutionTrace {
302        let mut executor = self.into_debug(package, source_manager.clone());
303        loop {
304            if executor.stopped {
305                break;
306            }
307            match executor.step() {
308                Ok(_) => {
309                    if log::log_enabled!(target: "executor", log::Level::Trace)
310                        && let (Some(op), Some(asmop)) =
311                            (executor.current_op, executor.current_asmop.as_ref())
312                    {
313                        log::trace!(target: "executor", "stack: {:?}", executor.current_stack);
314                        let source_loc = asmop.location().map(|loc| {
315                            let path = std::path::Path::new(loc.uri().path());
316                            let file = source_manager.load_file(path).unwrap();
317                            (file, loc.start)
318                        });
319                        if let Some((source_file, line_start)) = source_loc {
320                            let line_number = source_file.content().line_index(line_start).number();
321                            log::trace!(target: "executor", "in {} (located at {}:{})", asmop.context_name(), source_file.deref().uri().as_str(), line_number);
322                        } else {
323                            log::trace!(target: "executor", "in {} (no source location available)", asmop.context_name());
324                        }
325                        log::trace!(target: "executor", "  executed `{op:?}` of `{}` ({} cycles)", asmop.op(), asmop.num_cycles());
326                        log::trace!(target: "executor", "  stack state: {:#?}", executor.current_stack);
327                    }
328                }
329                Err(err) => {
330                    render_execution_error(err, &executor, &source_manager);
331                }
332            }
333        }
334
335        executor.into_execution_trace()
336    }
337
338    /// Execute a program, parsing the operand stack outputs as a value of type `T`
339    pub fn execute_into<T>(self, package: Arc<Package>, source_manager: Arc<dyn SourceManager>) -> T
340    where
341        T: FromMidenRepr + PartialEq,
342    {
343        let out = self.execute(package, source_manager);
344        out.parse_result().expect("invalid result")
345    }
346}
347
348#[derive(Debug, thiserror::Error)]
349enum PrintLnError {
350    #[error("address should fit in u32")]
351    InvalidAddress,
352    #[error("string length should fit in usize")]
353    InvalidLength,
354    #[error("string length {requested} exceeds maximum {max}")]
355    LengthExceeded { requested: usize, max: usize },
356    #[error("memory is not initialized")]
357    MemoryNotInitialized,
358    #[error("failed to read memory: {0}")]
359    MemoryRead(#[from] super::trace::MemoryReadError),
360    #[error("invalid UTF-8")]
361    InvalidUtf8,
362}
363
364fn register_builtin_event_handlers(
365    host: &mut DebuggerHost<dyn SourceManager>,
366    events: Arc<Mutex<BTreeMap<RowIndex, Event>>>,
367) {
368    let println_handler = |process: &ProcessorState| -> Result<Vec<AdviceMutation>, EventError> {
369        match decode_println(process) {
370            Ok(content) => {
371                log::log!(target: "stdout", Level::Info, "{content}");
372            }
373            Err(err) => {
374                log::warn!(
375                    target: "executor",
376                    "emit.{PRINTLN_EVENT} failed at cycle {}: {err}",
377                    process.clock(),
378                );
379            }
380        }
381
382        Ok(vec![])
383    };
384
385    host.register_event_handler(PRINTLN_EVENT, Arc::new(println_handler))
386        .expect("failed to register println event handler");
387
388    let frame_start_events = Arc::clone(&events);
389    let frame_start_handler =
390        move |process: &ProcessorState| -> Result<Vec<AdviceMutation>, EventError> {
391            frame_start_events.lock().unwrap().insert(process.clock(), Event::FrameStart);
392            Ok(vec![])
393        };
394    host.register_event_handler(FRAME_START_EVENT, Arc::new(frame_start_handler))
395        .expect("failed to register frame start event handler");
396
397    let frame_end_events = Arc::clone(&events);
398    let frame_end_handler =
399        move |process: &ProcessorState| -> Result<Vec<AdviceMutation>, EventError> {
400            frame_end_events.lock().unwrap().insert(process.clock(), Event::FrameEnd);
401            Ok(vec![])
402        };
403    host.register_event_handler(FRAME_END_EVENT, Arc::from(frame_end_handler))
404        .expect("failed to register frame end event handler");
405
406    /*
407    let assertion_events = Rc::clone(&events);
408    host.register_assert_failed_tracer(move |process, event| {
409        assertion_events.borrow_mut().insert(process.clock(), event);
410    });
411     */
412}
413
414/// Decode a [`Event::PrintLn`] event into a UTF-8 string.
415///
416/// Expects `[event_id, address, length]` on the operand stack. Reads `length` bytes from `address`
417/// in the current context's memory and returns them as a string.
418fn decode_println(process: &ProcessorState<'_>) -> Result<String, PrintLnError> {
419    let addr = u32::try_from(process.get_stack_item(1).as_canonical_u64())
420        .map_err(|_| PrintLnError::InvalidAddress)?;
421    let len = usize::try_from(process.get_stack_item(2).as_canonical_u64())
422        .map_err(|_| PrintLnError::InvalidLength)?;
423    if len > MAX_PRINTLN_BYTES {
424        return Err(PrintLnError::LengthExceeded {
425            requested: len,
426            max: MAX_PRINTLN_BYTES,
427        });
428    }
429    let ptr = NativePtr::from_ptr(addr);
430    let ctx = process.ctx();
431
432    let bytes = read_memory_bytes(ptr, len, |addr| {
433        process.get_mem_value(ctx, addr).ok_or(PrintLnError::MemoryNotInitialized)
434    })?;
435
436    String::from_utf8(bytes).map_err(|_| PrintLnError::InvalidUtf8)
437}
438
439#[track_caller]
440fn render_execution_error(
441    err: ExecutionError,
442    execution_state: &DebugExecutor,
443    source_manager: &dyn SourceManager,
444) -> ! {
445    use miden_assembly_syntax::diagnostics::{
446        LabeledSpan, miette::miette, reporting::PrintDiagnostic,
447    };
448
449    let stacktrace = execution_state.callstack.stacktrace(&execution_state.recent, source_manager);
450
451    eprintln!("{stacktrace}");
452
453    if !execution_state.current_stack.is_empty() {
454        let stack = execution_state.current_stack.iter().map(|elem| elem.as_canonical_u64());
455        let stack = DisplayValues::new(stack);
456        eprintln!(
457            "\nLast Known State (at most recent instruction which succeeded):
458 | Operand Stack: [{stack}]
459 "
460        );
461
462        let mut labels = vec![];
463        if let Some(span) = stacktrace
464            .current_frame()
465            .and_then(|frame| frame.location.as_ref())
466            .map(|loc| loc.span)
467        {
468            labels.push(LabeledSpan::new_with_span(
469                None,
470                span.start().to_usize()..span.end().to_usize(),
471            ));
472        }
473        let report = miette!(
474            labels = labels,
475            "program execution failed at step {step} (cycle {cycle}): {err}",
476            step = execution_state.cycle,
477            cycle = execution_state.cycle,
478        );
479        let report = match stacktrace
480            .current_frame()
481            .and_then(|frame| frame.location.as_ref())
482            .map(|loc| loc.source_file.clone())
483        {
484            Some(source) => report.with_source_code(source),
485            None => report,
486        };
487
488        panic!("{}", PrintDiagnostic::new(report));
489    } else {
490        panic!("program execution failed at step {step}: {err}", step = execution_state.cycle);
491    }
492}
493
494/// Render an iterator of `T`, comma-separated
495struct DisplayValues<T>(Cell<Option<T>>);
496
497impl<T> DisplayValues<T> {
498    pub fn new(inner: T) -> Self {
499        Self(Cell::new(Some(inner)))
500    }
501}
502
503impl<T, I> fmt::Display for DisplayValues<I>
504where
505    T: fmt::Display,
506    I: Iterator<Item = T>,
507{
508    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
509        let iter = self.0.take().unwrap();
510        for (i, item) in iter.enumerate() {
511            if i == 0 {
512                write!(f, "{item}")?;
513            } else {
514                write!(f, ", {item}")?;
515            }
516        }
517        Ok(())
518    }
519}
520
521#[cfg(test)]
522mod tests {
523    use super::*;
524
525    /// One entry per `on_event` invocation, in execution order, and the recorded log replays to
526    /// an identical result without the original event handlers.
527    #[test]
528    fn records_event_mutations_and_replays_them() {
529        use std::sync::atomic::{AtomicU64, Ordering};
530
531        use miden_assembly::DefaultSourceManager;
532        use miden_core::events::EventId;
533        use miden_processor::{ProcessorState, advice::AdviceMutation, event::EventError};
534
535        struct CountingHandler {
536            calls: AtomicU64,
537        }
538
539        impl EventHandler for CountingHandler {
540            fn on_event(
541                &self,
542                _process: &ProcessorState<'_>,
543            ) -> Result<Vec<AdviceMutation>, EventError> {
544                let call = self.calls.fetch_add(1, Ordering::SeqCst);
545                Ok(vec![AdviceMutation::ExtendStack {
546                    values: vec![Felt::from(100u32 + call as u32)],
547                }])
548            }
549        }
550
551        let source_manager: Arc<DefaultSourceManager> = Arc::new(DefaultSourceManager::default());
552        let event_name = "miden-debug::test::record-replay";
553        let event_id = EventId::from_name(event_name).as_u64();
554        // Each emit invokes the handler, which pushes one value onto the advice stack;
555        // adv_push moves it to the operand stack, and the sum of both values is the result.
556        let source = format!(
557            "begin push.{event_id} emit drop adv_push push.{event_id} emit drop adv_push add swap \
558             drop end"
559        );
560        let program = miden_assembly::Assembler::new(source_manager.clone())
561            .assemble_program("program", source)
562            .map(Arc::from)
563            .expect("failed to assemble test program");
564
565        let mut executor = Executor::new(Vec::new());
566        executor
567            .register_event_handler(
568                EventName::from_string(event_name.to_string()),
569                Arc::new(CountingHandler {
570                    calls: AtomicU64::new(0),
571                }),
572            )
573            .expect("failed to register event handler");
574        executor.with_event_advice_mutations_recording();
575
576        // Run to completion through the debug executor: recording is an internal detail of its
577        // host, and the log is taken from the host once execution finishes.
578        let mut debug_executor = executor.into_debug(Arc::clone(&program), source_manager.clone());
579        while !debug_executor.stopped {
580            debug_executor.step().expect("recording step failed");
581        }
582        let recorded = debug_executor.host.take_recorded_event_mutations();
583        let recorded_result: u32 =
584            debug_executor.into_execution_trace().parse_result().expect("invalid result");
585        assert_eq!(recorded_result, 201);
586
587        assert_eq!(recorded.len(), 2, "expected one recorded entry per emit");
588        for (index, batch) in recorded.iter().enumerate() {
589            match batch.as_slice() {
590                [AdviceMutation::ExtendStack { values }] => {
591                    assert_eq!(values.as_slice(), &[Felt::from(100u32 + index as u32)]);
592                }
593                _ => panic!("unexpected mutations recorded for event {index}"),
594            }
595        }
596
597        // Replay the recorded mutations without any event handlers registered: execution must
598        // reach the same result, proving the log is sufficient for event replay.
599        let replay_executor = Executor::new(Vec::new());
600        let mut debug_executor = replay_executor.into_debug_with_replay(
601            program,
602            source_manager,
603            Vec::new(),
604            recorded.into(),
605        );
606        while !debug_executor.stopped {
607            debug_executor.step().expect("replay step failed");
608        }
609        let replayed_result: u32 = debug_executor
610            .into_execution_trace()
611            .parse_result()
612            .expect("invalid replay result");
613        assert_eq!(replayed_result, recorded_result);
614    }
615
616    /// A recorded execution serialized into a [ReplaySnapshot](crate::exec::ReplaySnapshot) and
617    /// read back from bytes replays to the same result — the offline record→replay path, end to
618    /// end, exactly what `miden-debug --replay <snapshot>` drives.
619    #[test]
620    fn replays_from_a_serialized_snapshot() {
621        use std::sync::atomic::{AtomicU64, Ordering};
622
623        use miden_assembly::DefaultSourceManager;
624        use miden_core::events::EventId;
625        use miden_processor::{ProcessorState, advice::AdviceMutation, event::EventError};
626
627        use crate::exec::ReplaySnapshot;
628
629        struct CountingHandler {
630            calls: AtomicU64,
631        }
632
633        impl EventHandler for CountingHandler {
634            fn on_event(
635                &self,
636                _process: &ProcessorState<'_>,
637            ) -> Result<Vec<AdviceMutation>, EventError> {
638                let call = self.calls.fetch_add(1, Ordering::SeqCst);
639                Ok(vec![AdviceMutation::ExtendStack {
640                    values: vec![Felt::from(100u32 + call as u32)],
641                }])
642            }
643        }
644
645        let source_manager: Arc<DefaultSourceManager> = Arc::new(DefaultSourceManager::default());
646        let event_name = "miden-debug::test::snapshot-replay";
647        let event_id = EventId::from_name(event_name).as_u64();
648        let source = format!(
649            "begin push.{event_id} emit drop adv_push push.{event_id} emit drop adv_push add add \
650             end"
651        );
652        let program = miden_assembly::Assembler::new(source_manager.clone())
653            .assemble_program("program", source)
654            .map(Arc::<Package>::from)
655            .expect("failed to assemble test program");
656        let stack_inputs = StackInputs::new(&[Felt::from(7u32)]).unwrap();
657        let advice_inputs = AdviceInputs::default();
658        let options = ExecutionOptions::default();
659
660        // Record the event mutations by running to completion with a live handler.
661        let mut executor = Executor::from_config(ExecutionConfig {
662            inputs: stack_inputs,
663            advice_inputs: advice_inputs.clone(),
664            options,
665        });
666        executor
667            .register_event_handler(
668                EventName::from_string(event_name.to_string()),
669                Arc::new(CountingHandler {
670                    calls: AtomicU64::new(0),
671                }),
672            )
673            .expect("failed to register event handler");
674        executor.with_event_advice_mutations_recording();
675        let mut debug_executor = executor.into_debug(program.clone(), source_manager.clone());
676        while !debug_executor.stopped {
677            debug_executor.step().expect("recording step failed");
678        }
679        let event_log = debug_executor.host.take_recorded_event_mutations();
680        let recorded_result: u32 =
681            debug_executor.into_execution_trace().parse_result().expect("invalid result");
682
683        // Persist the recording as a snapshot and read it back from its serialized bytes.
684        let snapshot = ReplaySnapshot {
685            package: program.clone(),
686            stack_inputs,
687            advice_inputs,
688            options,
689            mast_forests: vec![LoadedMastForest::with_package_debug_info(
690                program.mast_forest().clone(),
691                program.debug_info(),
692            )],
693            event_log,
694        };
695        let restored = ReplaySnapshot::read_from_bytes(&snapshot.to_bytes())
696            .expect("snapshot failed to deserialize");
697
698        // Replay from the deserialized snapshot, with no event handlers registered.
699        let replay_executor = Executor::from_config(ExecutionConfig {
700            inputs: restored.stack_inputs,
701            advice_inputs: restored.advice_inputs,
702            options: restored.options,
703        });
704        let mut debug_executor = replay_executor.into_debug_with_replay(
705            restored.package.clone(),
706            source_manager,
707            restored.mast_forests.clone(),
708            restored.event_log.into(),
709        );
710        while !debug_executor.stopped {
711            debug_executor.step().expect("replay step failed");
712        }
713        let replayed_result: u32 = debug_executor
714            .into_execution_trace()
715            .parse_result()
716            .expect("invalid replay result");
717        assert_eq!(replayed_result, recorded_result);
718        assert_eq!(replayed_result, 208);
719    }
720}