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,
8};
9
10use miden_assembly_syntax::{Library, diagnostics::Report};
11use miden_core::{
12    Word,
13    program::{Program, StackInputs},
14};
15use miden_debug_types::{SourceManager, SourceManagerExt};
16use miden_mast_package::Dependency;
17use miden_processor::{
18    ContextId, ExecutionError, ExecutionOptions, FastProcessor, Felt,
19    advice::{AdviceInputs, AdviceMutation},
20    event::{EventHandler, EventName},
21    mast::MastForest,
22    trace::RowIndex,
23};
24
25use super::{DebugExecutor, DebuggerHost, ExecutionConfig, ExecutionTrace, TraceEvent};
26use crate::{debug::CallStack, felt::FromMidenRepr};
27
28/// The [Executor] is responsible for executing a program with the Miden VM.
29///
30/// It is used by either converting it into a [DebugExecutor], and using that to
31/// manage execution step-by-step, such as is done by the debugger; or by running
32/// the program to completion and obtaining an [ExecutionTrace], which can be used
33/// to introspect the final program state.
34pub struct Executor {
35    stack: StackInputs,
36    advice: AdviceInputs,
37    options: ExecutionOptions,
38    libraries: Vec<Arc<Library>>,
39    event_handlers: Vec<(EventName, Arc<dyn EventHandler>)>,
40    dependency_resolver: BTreeMap<Word, Arc<Library>>,
41}
42impl Executor {
43    /// Construct an executor with the given arguments on the operand stack
44    pub fn new(args: Vec<Felt>) -> Self {
45        let config = ExecutionConfig {
46            inputs: StackInputs::new(&args).expect("invalid stack inputs"),
47            ..Default::default()
48        };
49
50        Self::from_config(config)
51    }
52
53    /// Construct an executor from the given configuration
54    ///
55    /// NOTE: The execution options for tracing/debugging will be set to true for you
56    pub fn from_config(config: ExecutionConfig) -> Self {
57        let ExecutionConfig {
58            inputs,
59            advice_inputs,
60            options,
61        } = config;
62        let options = options.with_tracing(true).with_debugging(true);
63        let dependency_resolver = BTreeMap::new();
64
65        Self {
66            stack: inputs,
67            advice: advice_inputs,
68            options,
69            libraries: Default::default(),
70            event_handlers: Default::default(),
71            dependency_resolver,
72        }
73    }
74
75    /// Construct the executor with the given inputs and adds dependencies from the given package
76    pub fn for_package<I>(package: &miden_mast_package::Package, args: I) -> Result<Self, Report>
77    where
78        I: IntoIterator<Item = Felt>,
79    {
80        use miden_assembly_syntax::DisplayHex;
81        log::debug!(
82            "creating executor for package '{}' (digest={})",
83            package.name,
84            DisplayHex::new(&package.digest().as_bytes())
85        );
86        let mut exec = Self::new(args.into_iter().collect());
87        let dependencies = package.manifest.dependencies();
88        exec.with_dependencies(dependencies)?;
89        log::debug!("executor created");
90        Ok(exec)
91    }
92
93    /// Adds dependencies to the executor
94    pub fn with_dependencies<'a>(
95        &mut self,
96        dependencies: impl Iterator<Item = &'a Dependency>,
97    ) -> Result<&mut Self, Report> {
98        for dep in dependencies {
99            let digest = dep.digest;
100            match self.dependency_resolver.get(&digest) {
101                Some(lib) => {
102                    log::debug!("dependency {dep:?} resolved");
103                    self.with_library(lib.clone());
104                }
105                None => panic!("{dep:?} not found in resolver"),
106            }
107        }
108
109        log::debug!("executor created");
110
111        Ok(self)
112    }
113
114    /// Set the contents of memory for the shadow stack frame of the entrypoint
115    pub fn with_advice_inputs(&mut self, advice: AdviceInputs) -> &mut Self {
116        self.advice.extend(advice);
117        self
118    }
119
120    /// Add a [Library] to the execution context
121    pub fn with_library(&mut self, lib: Arc<Library>) -> &mut Self {
122        self.libraries.push(lib);
123        self
124    }
125
126    /// Register a VM event handler to be available during execution.
127    pub fn register_event_handler(
128        &mut self,
129        event: EventName,
130        handler: Arc<dyn EventHandler>,
131    ) -> Result<&mut Self, ExecutionError> {
132        self.event_handlers.push((event, handler));
133        Ok(self)
134    }
135
136    /// Convert this [Executor] into a [DebugExecutor], which captures much more information
137    /// about the program being executed, and must be stepped manually.
138    pub fn into_debug(
139        mut self,
140        program: &Program,
141        source_manager: Arc<dyn SourceManager>,
142    ) -> DebugExecutor {
143        log::debug!("creating debug executor");
144
145        let mut host = DebuggerHost::new(source_manager.clone());
146        for lib in core::mem::take(&mut self.libraries) {
147            host.load_mast_forest(lib.mast_forest().clone());
148        }
149        for (event, handler) in core::mem::take(&mut self.event_handlers) {
150            host.register_event_handler(event, handler)
151                .expect("failed to register debug executor event handler");
152        }
153
154        let trace_events: Rc<RefCell<BTreeMap<RowIndex, TraceEvent>>> = Rc::new(Default::default());
155        let frame_start_events = Rc::clone(&trace_events);
156        host.register_trace_handler(TraceEvent::FrameStart, move |clk, event| {
157            frame_start_events.borrow_mut().insert(clk, event);
158        });
159        let frame_end_events = Rc::clone(&trace_events);
160        host.register_trace_handler(TraceEvent::FrameEnd, move |clk, event| {
161            frame_end_events.borrow_mut().insert(clk, event);
162        });
163        let assertion_events = Rc::clone(&trace_events);
164        host.register_assert_failed_tracer(move |clk, event| {
165            assertion_events.borrow_mut().insert(clk, event);
166        });
167
168        let mut processor = FastProcessor::new(self.stack)
169            .with_advice(self.advice)
170            .with_options(self.options)
171            .with_debugging(true)
172            .with_tracing(true);
173
174        let root_context = ContextId::root();
175        let resume_ctx = processor
176            .get_initial_resume_context(program)
177            .expect("failed to get initial resume context");
178
179        let callstack = CallStack::new(trace_events);
180        DebugExecutor {
181            processor,
182            host,
183            resume_ctx: Some(resume_ctx),
184            current_stack: vec![],
185            current_op: None,
186            current_asmop: None,
187            stack_outputs: Default::default(),
188            contexts: Default::default(),
189            root_context,
190            current_context: root_context,
191            callstack,
192            recent: VecDeque::with_capacity(5),
193            cycle: 0,
194            stopped: false,
195        }
196    }
197
198    /// Convert this [Executor] into a [DebugExecutor] with event replay support.
199    ///
200    /// Like [`into_debug`](Self::into_debug), but additionally:
201    /// - Loads `extra_forests` into the host's MAST forest store
202    /// - Sets the event replay queue so that `on_event()` returns pre-recorded mutations
203    ///
204    /// This is used for transaction debugging where events were recorded during a prior
205    /// execution with the real transaction host.
206    pub fn into_debug_with_replay(
207        mut self,
208        program: &Program,
209        source_manager: Arc<dyn SourceManager>,
210        extra_forests: Vec<Arc<MastForest>>,
211        event_replay: VecDeque<Vec<AdviceMutation>>,
212    ) -> DebugExecutor {
213        log::debug!("creating debug executor with event replay");
214
215        let mut host = DebuggerHost::new(source_manager.clone());
216        for lib in core::mem::take(&mut self.libraries) {
217            host.load_mast_forest(lib.mast_forest().clone());
218        }
219        for forest in extra_forests {
220            host.load_mast_forest(forest);
221        }
222        host.set_event_replay(event_replay);
223
224        let trace_events: Rc<RefCell<BTreeMap<RowIndex, TraceEvent>>> = Rc::new(Default::default());
225        let frame_start_events = Rc::clone(&trace_events);
226        host.register_trace_handler(TraceEvent::FrameStart, move |clk, event| {
227            frame_start_events.borrow_mut().insert(clk, event);
228        });
229        let frame_end_events = Rc::clone(&trace_events);
230        host.register_trace_handler(TraceEvent::FrameEnd, move |clk, event| {
231            frame_end_events.borrow_mut().insert(clk, event);
232        });
233        let assertion_events = Rc::clone(&trace_events);
234        host.register_assert_failed_tracer(move |clk, event| {
235            assertion_events.borrow_mut().insert(clk, event);
236        });
237
238        let mut processor = FastProcessor::new(self.stack)
239            .with_advice(self.advice)
240            .with_options(self.options)
241            .with_debugging(true)
242            .with_tracing(true);
243
244        let root_context = ContextId::root();
245        let resume_ctx = processor
246            .get_initial_resume_context(program)
247            .expect("failed to get initial resume context");
248
249        let callstack = CallStack::new(trace_events);
250        DebugExecutor {
251            processor,
252            host,
253            resume_ctx: Some(resume_ctx),
254            current_stack: vec![],
255            current_op: None,
256            current_asmop: None,
257            stack_outputs: Default::default(),
258            contexts: Default::default(),
259            root_context,
260            current_context: root_context,
261            callstack,
262            recent: VecDeque::with_capacity(5),
263            cycle: 0,
264            stopped: false,
265        }
266    }
267
268    /// Execute the given program until termination, producing a trace
269    pub fn capture_trace(
270        self,
271        program: &Program,
272        source_manager: Arc<dyn SourceManager>,
273    ) -> ExecutionTrace {
274        let mut executor = self.into_debug(program, source_manager);
275        loop {
276            if executor.stopped {
277                break;
278            }
279            match executor.step() {
280                Ok(_) => continue,
281                Err(_) => break,
282            }
283        }
284        executor.into_execution_trace()
285    }
286
287    /// Execute the given program, producing a trace
288    #[track_caller]
289    pub fn execute(
290        self,
291        program: &Program,
292        source_manager: Arc<dyn SourceManager>,
293    ) -> ExecutionTrace {
294        let mut executor = self.into_debug(program, source_manager.clone());
295        loop {
296            if executor.stopped {
297                break;
298            }
299            match executor.step() {
300                Ok(_) => {
301                    if log::log_enabled!(target: "executor", log::Level::Trace)
302                        && let (Some(op), Some(asmop)) =
303                            (executor.current_op, executor.current_asmop.as_ref())
304                    {
305                        dbg!(&executor.current_stack);
306                        let source_loc = asmop.location().map(|loc| {
307                            let path = std::path::Path::new(loc.uri().path());
308                            let file = source_manager.load_file(path).unwrap();
309                            (file, loc.start)
310                        });
311                        if let Some((source_file, line_start)) = source_loc {
312                            let line_number = source_file.content().line_index(line_start).number();
313                            log::trace!(target: "executor", "in {} (located at {}:{})", asmop.context_name(), source_file.deref().uri().as_str(), &line_number);
314                        } else {
315                            log::trace!(target: "executor", "in {} (no source location available)", asmop.context_name());
316                        }
317                        log::trace!(target: "executor", "  executed `{op:?}` of `{}` ({} cycles)", asmop.op(), asmop.num_cycles());
318                        log::trace!(target: "executor", "  stack state: {:#?}", &executor.current_stack);
319                    }
320                }
321                Err(err) => {
322                    render_execution_error(err, &executor, &source_manager);
323                }
324            }
325        }
326
327        executor.into_execution_trace()
328    }
329
330    /// Execute a program, parsing the operand stack outputs as a value of type `T`
331    pub fn execute_into<T>(self, program: &Program, source_manager: Arc<dyn SourceManager>) -> T
332    where
333        T: FromMidenRepr + PartialEq,
334    {
335        let out = self.execute(program, source_manager);
336        out.parse_result().expect("invalid result")
337    }
338
339    pub fn dependency_resolver_mut(&mut self) -> &mut BTreeMap<Word, Arc<Library>> {
340        &mut self.dependency_resolver
341    }
342
343    /// Register a library with the dependency resolver so it can be found when resolving package dependencies
344    pub fn register_library_dependency(&mut self, lib: Arc<Library>) {
345        let digest = *lib.digest();
346        self.dependency_resolver.insert(digest, lib);
347    }
348}
349
350#[track_caller]
351fn render_execution_error(
352    err: ExecutionError,
353    execution_state: &DebugExecutor,
354    source_manager: &dyn SourceManager,
355) -> ! {
356    use miden_assembly_syntax::diagnostics::{
357        LabeledSpan, miette::miette, reporting::PrintDiagnostic,
358    };
359
360    let stacktrace = execution_state.callstack.stacktrace(&execution_state.recent, source_manager);
361
362    eprintln!("{stacktrace}");
363
364    if !execution_state.current_stack.is_empty() {
365        let stack = execution_state.current_stack.iter().map(|elem| elem.as_canonical_u64());
366        let stack = DisplayValues::new(stack);
367        eprintln!(
368            "\nLast Known State (at most recent instruction which succeeded):
369 | Operand Stack: [{stack}]
370 "
371        );
372
373        let mut labels = vec![];
374        if let Some(span) = stacktrace
375            .current_frame()
376            .and_then(|frame| frame.location.as_ref())
377            .map(|loc| loc.span)
378        {
379            labels.push(LabeledSpan::new_with_span(
380                None,
381                span.start().to_usize()..span.end().to_usize(),
382            ));
383        }
384        let report = miette!(
385            labels = labels,
386            "program execution failed at step {step} (cycle {cycle}): {err}",
387            step = execution_state.cycle,
388            cycle = execution_state.cycle,
389        );
390        let report = match stacktrace
391            .current_frame()
392            .and_then(|frame| frame.location.as_ref())
393            .map(|loc| loc.source_file.clone())
394        {
395            Some(source) => report.with_source_code(source),
396            None => report,
397        };
398
399        panic!("{}", PrintDiagnostic::new(report));
400    } else {
401        panic!("program execution failed at step {step}: {err}", step = execution_state.cycle);
402    }
403}
404
405/// Render an iterator of `T`, comma-separated
406struct DisplayValues<T>(Cell<Option<T>>);
407
408impl<T> DisplayValues<T> {
409    pub fn new(inner: T) -> Self {
410        Self(Cell::new(Some(inner)))
411    }
412}
413
414impl<T, I> fmt::Display for DisplayValues<I>
415where
416    T: fmt::Display,
417    I: Iterator<Item = T>,
418{
419    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
420        let iter = self.0.take().unwrap();
421        for (i, item) in iter.enumerate() {
422            if i == 0 {
423                write!(f, "{item}")?;
424            } else {
425                write!(f, ", {item}")?;
426            }
427        }
428        Ok(())
429    }
430}