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