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
25pub struct Executor {
32 stack: StackInputs,
33 advice: AdviceInputs,
34 options: ExecutionOptions,
35 libraries: Vec<Arc<Library>>,
36 dependency_resolver: MemDependencyResolverByDigest,
37}
38impl Executor {
39 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 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 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 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 pub fn with_advice_inputs(&mut self, advice: AdviceInputs) -> &mut Self {
125 self.advice.extend(advice);
126 self
127 }
128
129 pub fn with_library(&mut self, lib: Arc<Library>) -> &mut Self {
131 self.libraries.push(lib);
132 self
133 }
134
135 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 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 #[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 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 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
331struct 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}