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::{ast::DebugVarInfo, diagnostics::Report};
12use miden_core::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    // Keep builtin event handlers in sync with `Event::has_builtin_handler`
386
387    host.register_event_handler(PRINTLN_EVENT, Arc::new(println_handler))
388        .expect("failed to register println event handler");
389
390    let frame_start_events = Arc::clone(&events);
391    let frame_start_handler =
392        move |process: &ProcessorState| -> Result<Vec<AdviceMutation>, EventError> {
393            frame_start_events.lock().unwrap().insert(process.clock(), Event::FrameStart);
394            Ok(vec![])
395        };
396    host.register_event_handler(FRAME_START_EVENT, Arc::new(frame_start_handler))
397        .expect("failed to register frame start event handler");
398
399    let frame_end_events = Arc::clone(&events);
400    let frame_end_handler =
401        move |process: &ProcessorState| -> Result<Vec<AdviceMutation>, EventError> {
402            frame_end_events.lock().unwrap().insert(process.clock(), Event::FrameEnd);
403            Ok(vec![])
404        };
405    host.register_event_handler(FRAME_END_EVENT, Arc::from(frame_end_handler))
406        .expect("failed to register frame end event handler");
407
408    /*
409    let assertion_events = Rc::clone(&events);
410    host.register_assert_failed_tracer(move |process, event| {
411        assertion_events.borrow_mut().insert(process.clock(), event);
412    });
413     */
414}
415
416/// Decode a [`Event::PrintLn`] event into a UTF-8 string.
417///
418/// Expects `[event_id, address, length]` on the operand stack. Reads `length` bytes from `address`
419/// in the current context's memory and returns them as a string.
420fn decode_println(process: &ProcessorState<'_>) -> Result<String, PrintLnError> {
421    let addr = u32::try_from(process.get_stack_item(1).as_canonical_u64())
422        .map_err(|_| PrintLnError::InvalidAddress)?;
423    let len = usize::try_from(process.get_stack_item(2).as_canonical_u64())
424        .map_err(|_| PrintLnError::InvalidLength)?;
425    if len > MAX_PRINTLN_BYTES {
426        return Err(PrintLnError::LengthExceeded {
427            requested: len,
428            max: MAX_PRINTLN_BYTES,
429        });
430    }
431    let ptr = NativePtr::from_ptr(addr);
432    let ctx = process.ctx();
433
434    let bytes = read_memory_bytes(ptr, len, |addr| {
435        process.get_mem_value(ctx, addr).ok_or(PrintLnError::MemoryNotInitialized)
436    })?;
437
438    String::from_utf8(bytes).map_err(|_| PrintLnError::InvalidUtf8)
439}
440
441#[track_caller]
442fn render_execution_error(
443    err: ExecutionError,
444    execution_state: &DebugExecutor,
445    source_manager: &dyn SourceManager,
446) -> ! {
447    use miden_assembly_syntax::diagnostics::{
448        LabeledSpan, miette::miette, reporting::PrintDiagnostic,
449    };
450
451    let stacktrace = execution_state.callstack.stacktrace(&execution_state.recent, source_manager);
452
453    eprintln!("{stacktrace}");
454
455    if !execution_state.current_stack.is_empty() {
456        let stack = execution_state.current_stack.iter().map(|elem| elem.as_canonical_u64());
457        let stack = DisplayValues::new(stack);
458        eprintln!(
459            "\nLast Known State (at most recent instruction which succeeded):
460 | Operand Stack: [{stack}]
461 "
462        );
463
464        let mut labels = vec![];
465        if let Some(span) = stacktrace
466            .current_frame()
467            .and_then(|frame| frame.location.as_ref())
468            .map(|loc| loc.span)
469        {
470            labels.push(LabeledSpan::new_with_span(
471                None,
472                span.start().to_usize()..span.end().to_usize(),
473            ));
474        }
475        let report = miette!(
476            labels = labels,
477            "program execution failed at step {step} (cycle {cycle}): {err}",
478            step = execution_state.cycle,
479            cycle = execution_state.cycle,
480        );
481        let report = match stacktrace
482            .current_frame()
483            .and_then(|frame| frame.location.as_ref())
484            .map(|loc| loc.source_file.clone())
485        {
486            Some(source) => report.with_source_code(source),
487            None => report,
488        };
489
490        panic!("{}", PrintDiagnostic::new(report));
491    } else {
492        panic!("program execution failed at step {step}: {err}", step = execution_state.cycle);
493    }
494}
495
496/// Render an iterator of `T`, comma-separated
497struct DisplayValues<T>(Cell<Option<T>>);
498
499impl<T> DisplayValues<T> {
500    pub fn new(inner: T) -> Self {
501        Self(Cell::new(Some(inner)))
502    }
503}
504
505impl<T, I> fmt::Display for DisplayValues<I>
506where
507    T: fmt::Display,
508    I: Iterator<Item = T>,
509{
510    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
511        let iter = self.0.take().unwrap();
512        for (i, item) in iter.enumerate() {
513            if i == 0 {
514                write!(f, "{item}")?;
515            } else {
516                write!(f, ", {item}")?;
517            }
518        }
519        Ok(())
520    }
521}
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526
527    /// One entry per `on_event` invocation, in execution order, and the recorded log replays to
528    /// an identical result without the original event handlers.
529    #[test]
530    fn records_event_mutations_and_replays_them() {
531        use std::sync::atomic::{AtomicU64, Ordering};
532
533        use miden_assembly::DefaultSourceManager;
534        use miden_core::events::EventId;
535        use miden_processor::{ProcessorState, advice::AdviceMutation, event::EventError};
536
537        struct CountingHandler {
538            calls: AtomicU64,
539        }
540
541        impl EventHandler for CountingHandler {
542            fn on_event(
543                &self,
544                _process: &ProcessorState<'_>,
545            ) -> Result<Vec<AdviceMutation>, EventError> {
546                let call = self.calls.fetch_add(1, Ordering::SeqCst);
547                Ok(vec![AdviceMutation::ExtendStack {
548                    values: vec![Felt::from(100u32 + call as u32)],
549                }])
550            }
551        }
552
553        let source_manager: Arc<DefaultSourceManager> = Arc::new(DefaultSourceManager::default());
554        let event_name = "miden-debug::test::record-replay";
555        let event_id = EventId::from_name(event_name).as_u64();
556        // Each emit invokes the handler, which pushes one value onto the advice stack;
557        // adv_push moves it to the operand stack, and the sum of both values is the result.
558        let source = format!(
559            "begin push.{event_id} emit drop adv_push push.{event_id} emit drop adv_push add swap \
560             drop end"
561        );
562        let program = miden_assembly::Assembler::new(source_manager.clone())
563            .assemble_program("program", source)
564            .map(Arc::from)
565            .expect("failed to assemble test program");
566
567        let mut executor = Executor::new(Vec::new());
568        executor
569            .register_event_handler(
570                EventName::from_string(event_name.to_string()),
571                Arc::new(CountingHandler {
572                    calls: AtomicU64::new(0),
573                }),
574            )
575            .expect("failed to register event handler");
576        executor.with_event_advice_mutations_recording();
577
578        // Run to completion through the debug executor: recording is an internal detail of its
579        // host, and the log is taken from the host once execution finishes.
580        let mut debug_executor = executor.into_debug(Arc::clone(&program), source_manager.clone());
581        while !debug_executor.stopped {
582            debug_executor.step().expect("recording step failed");
583        }
584        let recorded = debug_executor.host.take_recorded_event_mutations();
585        let recorded_result: u32 =
586            debug_executor.into_execution_trace().parse_result().expect("invalid result");
587        assert_eq!(recorded_result, 201);
588
589        assert_eq!(recorded.len(), 2, "expected one recorded entry per emit");
590        for (index, batch) in recorded.iter().enumerate() {
591            match batch.as_slice() {
592                [AdviceMutation::ExtendStack { values }] => {
593                    assert_eq!(values.as_slice(), &[Felt::from(100u32 + index as u32)]);
594                }
595                _ => panic!("unexpected mutations recorded for event {index}"),
596            }
597        }
598
599        // Replay the recorded mutations without any event handlers registered: execution must
600        // reach the same result, proving the log is sufficient for event replay.
601        let replay_executor = Executor::new(Vec::new());
602        let mut debug_executor = replay_executor.into_debug_with_replay(
603            program,
604            source_manager,
605            Vec::new(),
606            recorded.into(),
607        );
608        while !debug_executor.stopped {
609            debug_executor.step().expect("replay step failed");
610        }
611        let replayed_result: u32 = debug_executor
612            .into_execution_trace()
613            .parse_result()
614            .expect("invalid replay result");
615        assert_eq!(replayed_result, recorded_result);
616    }
617
618    /// Replayed builtin events still reach their handlers so debugger state remains available.
619    #[test]
620    fn replay_invokes_builtin_event_handlers() {
621        use miden_assembly::DefaultSourceManager;
622
623        let source_manager: Arc<DefaultSourceManager> = Arc::new(DefaultSourceManager::default());
624        let program = miden_assembly::Assembler::new(source_manager.clone())
625            .assemble_program(
626                "program",
627                format!(
628                    r#"
629begin
630    emit.event("{FRAME_START_EVENT}")
631    emit.event("{FRAME_START_EVENT}")
632end
633"#
634                ),
635            )
636            .map(Arc::<Package>::from)
637            .expect("failed to assemble test program");
638
639        let event_replay = VecDeque::from([Vec::new(), Vec::new()]);
640        let mut debug_executor = Executor::new(Vec::new()).into_debug_with_replay(
641            program,
642            source_manager,
643            Vec::new(),
644            event_replay,
645        );
646        while !debug_executor.stopped {
647            debug_executor.step().expect("replay step failed");
648        }
649
650        assert!(
651            debug_executor.callstack.frames().len() >= 2,
652            "expected replayed frame-start events to update the debugger call stack"
653        );
654    }
655
656    /// A recorded execution serialized into a [ReplaySnapshot](crate::exec::ReplaySnapshot) and
657    /// read back from bytes replays to the same result — the offline record→replay path, end to
658    /// end, exactly what `miden-debug --replay <snapshot>` drives.
659    #[test]
660    fn replays_from_a_serialized_snapshot() {
661        use std::sync::atomic::{AtomicU64, Ordering};
662
663        use miden_assembly::DefaultSourceManager;
664        use miden_core::events::EventId;
665        use miden_processor::{ProcessorState, advice::AdviceMutation, event::EventError};
666
667        use crate::exec::ReplaySnapshot;
668
669        struct CountingHandler {
670            calls: AtomicU64,
671        }
672
673        impl EventHandler for CountingHandler {
674            fn on_event(
675                &self,
676                _process: &ProcessorState<'_>,
677            ) -> Result<Vec<AdviceMutation>, EventError> {
678                let call = self.calls.fetch_add(1, Ordering::SeqCst);
679                Ok(vec![AdviceMutation::ExtendStack {
680                    values: vec![Felt::from(100u32 + call as u32)],
681                }])
682            }
683        }
684
685        let source_manager: Arc<DefaultSourceManager> = Arc::new(DefaultSourceManager::default());
686        let event_name = "miden-debug::test::snapshot-replay";
687        let event_id = EventId::from_name(event_name).as_u64();
688        let source = format!(
689            "begin push.{event_id} emit drop adv_push push.{event_id} emit drop adv_push add add \
690             end"
691        );
692        let program = miden_assembly::Assembler::new(source_manager.clone())
693            .assemble_program("program", source)
694            .map(Arc::<Package>::from)
695            .expect("failed to assemble test program");
696        let stack_inputs = StackInputs::new(&[Felt::from(7u32)]).unwrap();
697        let advice_inputs = AdviceInputs::default();
698        let options = ExecutionOptions::default();
699
700        // Record the event mutations by running to completion with a live handler.
701        let mut executor = Executor::from_config(ExecutionConfig {
702            inputs: stack_inputs,
703            advice_inputs: advice_inputs.clone(),
704            options,
705        });
706        executor
707            .register_event_handler(
708                EventName::from_string(event_name.to_string()),
709                Arc::new(CountingHandler {
710                    calls: AtomicU64::new(0),
711                }),
712            )
713            .expect("failed to register event handler");
714        executor.with_event_advice_mutations_recording();
715        let mut debug_executor = executor.into_debug(program.clone(), source_manager.clone());
716        while !debug_executor.stopped {
717            debug_executor.step().expect("recording step failed");
718        }
719        let event_log = debug_executor.host.take_recorded_event_mutations();
720        let recorded_result: u32 =
721            debug_executor.into_execution_trace().parse_result().expect("invalid result");
722
723        // Persist the recording as a snapshot and read it back from its serialized bytes.
724        let snapshot = ReplaySnapshot {
725            package: program.clone(),
726            stack_inputs,
727            advice_inputs,
728            options,
729            mast_forests: vec![LoadedMastForest::with_package_debug_info(
730                program.mast_forest().clone(),
731                program.debug_info(),
732            )],
733            event_log,
734        };
735        let restored = ReplaySnapshot::read_from_bytes(&snapshot.to_bytes())
736            .expect("snapshot failed to deserialize");
737
738        // Replay from the deserialized snapshot, with no event handlers registered.
739        let replay_executor = Executor::from_config(ExecutionConfig {
740            inputs: restored.stack_inputs,
741            advice_inputs: restored.advice_inputs,
742            options: restored.options,
743        });
744        let mut debug_executor = replay_executor.into_debug_with_replay(
745            restored.package.clone(),
746            source_manager,
747            restored.mast_forests.clone(),
748            restored.event_log.into(),
749        );
750        while !debug_executor.stopped {
751            debug_executor.step().expect("replay step failed");
752        }
753        let replayed_result: u32 = debug_executor
754            .into_execution_trace()
755            .parse_result()
756            .expect("invalid replay result");
757        assert_eq!(replayed_result, recorded_result);
758        assert_eq!(replayed_result, 208);
759    }
760}