Skip to main content

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