1use std::{
2 cell::{Cell, RefCell},
3 collections::{BTreeMap, VecDeque},
4 fmt,
5 ops::Deref,
6 rc::Rc,
7 sync::Arc,
8};
9
10use log::Level;
11use miden_assembly_syntax::{Library, diagnostics::Report};
12use miden_core::{
13 Word,
14 operations::DebugVarInfo,
15 program::{Program, StackInputs},
16};
17use miden_debug_types::{SourceManager, SourceManagerExt};
18use miden_mast_package::Dependency;
19use miden_processor::{
20 ContextId, ExecutionError, ExecutionOptions, FastProcessor, Felt, ProcessorState,
21 advice::{AdviceInputs, AdviceMutation},
22 event::{EventHandler, EventName},
23 mast::MastForest,
24 trace::RowIndex,
25};
26
27use super::{
28 DebugExecutor, DebuggerHost, ExecutionConfig, ExecutionTrace, TraceEvent,
29 query::read_memory_bytes, trace_event::TRACE_PRINT_LN,
30};
31use crate::{
32 debug::{CallStack, DebugVarTracker, NativePtr},
33 felt::FromMidenRepr,
34};
35
36const MAX_PRINTLN_BYTES: usize = 512 * 1024;
43
44pub struct Executor {
51 stack: StackInputs,
52 advice: AdviceInputs,
53 options: ExecutionOptions,
54 libraries: Vec<Arc<Library>>,
55 event_handlers: Vec<(EventName, Arc<dyn EventHandler>)>,
56 dependency_resolver: BTreeMap<Word, Arc<Library>>,
57}
58impl Executor {
59 pub fn new(args: Vec<Felt>) -> Self {
61 let config = ExecutionConfig {
62 inputs: StackInputs::new(&args).expect("invalid stack inputs"),
63 ..Default::default()
64 };
65
66 Self::from_config(config)
67 }
68
69 pub fn from_config(config: ExecutionConfig) -> Self {
73 let ExecutionConfig {
74 inputs,
75 advice_inputs,
76 options,
77 } = config;
78 let options = options.with_tracing(true).with_debugging(true);
79 let dependency_resolver = BTreeMap::new();
80
81 Self {
82 stack: inputs,
83 advice: advice_inputs,
84 options,
85 libraries: Default::default(),
86 event_handlers: Default::default(),
87 dependency_resolver,
88 }
89 }
90
91 pub fn for_package<I>(package: &miden_mast_package::Package, args: I) -> Result<Self, Report>
93 where
94 I: IntoIterator<Item = Felt>,
95 {
96 use miden_assembly_syntax::DisplayHex;
97 log::debug!(
98 "creating executor for package '{}' (digest={})",
99 package.name,
100 DisplayHex::new(&package.digest().as_bytes())
101 );
102 let mut exec = Self::new(args.into_iter().collect());
103 let dependencies = package.manifest.dependencies();
104 exec.with_dependencies(dependencies)?;
105 log::debug!("executor created");
106 Ok(exec)
107 }
108
109 pub fn with_dependencies<'a>(
111 &mut self,
112 dependencies: impl Iterator<Item = &'a Dependency>,
113 ) -> Result<&mut Self, Report> {
114 for dep in dependencies {
115 let digest = dep.digest;
116 match self.dependency_resolver.get(&digest) {
117 Some(lib) => {
118 log::debug!("dependency {dep:?} resolved");
119 self.with_library(lib.clone());
120 }
121 None => panic!("{dep:?} not found in resolver"),
122 }
123 }
124
125 log::debug!("executor created");
126
127 Ok(self)
128 }
129
130 pub fn with_advice_inputs(&mut self, advice: AdviceInputs) -> &mut Self {
132 self.advice.extend(advice);
133 self
134 }
135
136 pub fn with_library(&mut self, lib: Arc<Library>) -> &mut Self {
138 self.libraries.push(lib);
139 self
140 }
141
142 pub fn register_event_handler(
144 &mut self,
145 event: EventName,
146 handler: Arc<dyn EventHandler>,
147 ) -> Result<&mut Self, ExecutionError> {
148 self.event_handlers.push((event, handler));
149 Ok(self)
150 }
151
152 pub fn into_debug(
155 mut self,
156 program: &Program,
157 source_manager: Arc<dyn SourceManager>,
158 ) -> DebugExecutor {
159 log::debug!("creating debug executor");
160
161 let mut host = DebuggerHost::new(source_manager.clone());
162 for lib in core::mem::take(&mut self.libraries) {
163 host.load_mast_forest(lib.mast_forest().clone());
164 }
165 for (event, handler) in core::mem::take(&mut self.event_handlers) {
166 host.register_event_handler(event, handler)
167 .expect("failed to register debug executor event handler");
168 }
169
170 let trace_events: Rc<RefCell<BTreeMap<RowIndex, TraceEvent>>> = Rc::new(Default::default());
171 register_builtin_trace_handlers(&mut host, Rc::clone(&trace_events));
172
173 let debug_var_events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>> =
177 Rc::new(Default::default());
178
179 let mut processor = FastProcessor::new_with_options(self.stack, self.advice, self.options)
180 .expect("advice inputs should fit advice map limits")
181 .with_debugging(true)
182 .with_tracing(true);
183
184 let root_context = ContextId::root();
185 let resume_ctx = processor
186 .get_initial_resume_context(program)
187 .expect("failed to get initial resume context");
188
189 let callstack = CallStack::new(trace_events);
190 let debug_vars = DebugVarTracker::new(debug_var_events);
191 DebugExecutor {
192 processor,
193 host,
194 resume_ctx: Some(resume_ctx),
195 current_stack: vec![],
196 current_op: None,
197 current_asmop: None,
198 stack_outputs: Default::default(),
199 contexts: Default::default(),
200 root_context,
201 current_context: root_context,
202 callstack,
203 current_proc: None,
204 debug_vars,
205 last_debug_var_count: 0,
206 recent: VecDeque::with_capacity(5),
207 cycle: 0,
208 stopped: false,
209 }
210 }
211
212 pub fn into_debug_with_replay(
221 mut self,
222 program: &Program,
223 source_manager: Arc<dyn SourceManager>,
224 extra_forests: Vec<Arc<MastForest>>,
225 event_replay: VecDeque<Vec<AdviceMutation>>,
226 ) -> DebugExecutor {
227 log::debug!("creating debug executor with event replay");
228
229 let mut host = DebuggerHost::new(source_manager.clone());
230 for lib in core::mem::take(&mut self.libraries) {
231 host.load_mast_forest(lib.mast_forest().clone());
232 }
233 for forest in extra_forests {
234 host.load_mast_forest(forest);
235 }
236 host.set_event_replay(event_replay);
237
238 let debug_var_events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>> =
239 Rc::new(Default::default());
240
241 let trace_events: Rc<RefCell<BTreeMap<RowIndex, TraceEvent>>> = Rc::new(Default::default());
242 register_builtin_trace_handlers(&mut host, Rc::clone(&trace_events));
243
244 let mut processor = FastProcessor::new_with_options(self.stack, self.advice, self.options)
245 .expect("advice inputs should fit advice map limits")
246 .with_debugging(true)
247 .with_tracing(true);
248
249 let root_context = ContextId::root();
250 let resume_ctx = processor
251 .get_initial_resume_context(program)
252 .expect("failed to get initial resume context");
253
254 let callstack = CallStack::new(trace_events);
255 let debug_vars = DebugVarTracker::new(debug_var_events);
256 DebugExecutor {
257 processor,
258 host,
259 resume_ctx: Some(resume_ctx),
260 current_stack: vec![],
261 current_op: None,
262 current_asmop: None,
263 stack_outputs: Default::default(),
264 contexts: Default::default(),
265 root_context,
266 current_context: root_context,
267 callstack,
268 current_proc: None,
269 debug_vars,
270 last_debug_var_count: 0,
271 recent: VecDeque::with_capacity(5),
272 cycle: 0,
273 stopped: false,
274 }
275 }
276
277 pub fn capture_trace(
279 self,
280 program: &Program,
281 source_manager: Arc<dyn SourceManager>,
282 ) -> ExecutionTrace {
283 let mut executor = self.into_debug(program, source_manager);
284 loop {
285 if executor.stopped {
286 break;
287 }
288 match executor.step() {
289 Ok(_) => continue,
290 Err(err) => {
291 log::warn!(
292 target: "executor",
293 "capture_trace stopped early at cycle {}: {err}",
294 executor.cycle,
295 );
296 break;
297 }
298 }
299 }
300 executor.into_execution_trace()
301 }
302
303 #[track_caller]
305 pub fn execute(
306 self,
307 program: &Program,
308 source_manager: Arc<dyn SourceManager>,
309 ) -> ExecutionTrace {
310 let mut executor = self.into_debug(program, source_manager.clone());
311 loop {
312 if executor.stopped {
313 break;
314 }
315 match executor.step() {
316 Ok(_) => {
317 if log::log_enabled!(target: "executor", log::Level::Trace)
318 && let (Some(op), Some(asmop)) =
319 (executor.current_op, executor.current_asmop.as_ref())
320 {
321 dbg!(&executor.current_stack);
322 let source_loc = asmop.location().map(|loc| {
323 let path = std::path::Path::new(loc.uri().path());
324 let file = source_manager.load_file(path).unwrap();
325 (file, loc.start)
326 });
327 if let Some((source_file, line_start)) = source_loc {
328 let line_number = source_file.content().line_index(line_start).number();
329 log::trace!(target: "executor", "in {} (located at {}:{})", asmop.context_name(), source_file.deref().uri().as_str(), &line_number);
330 } else {
331 log::trace!(target: "executor", "in {} (no source location available)", asmop.context_name());
332 }
333 log::trace!(target: "executor", " executed `{op:?}` of `{}` ({} cycles)", asmop.op(), asmop.num_cycles());
334 log::trace!(target: "executor", " stack state: {:#?}", &executor.current_stack);
335 }
336 }
337 Err(err) => {
338 render_execution_error(err, &executor, &source_manager);
339 }
340 }
341 }
342
343 executor.into_execution_trace()
344 }
345
346 pub fn execute_into<T>(self, program: &Program, source_manager: Arc<dyn SourceManager>) -> T
348 where
349 T: FromMidenRepr + PartialEq,
350 {
351 let out = self.execute(program, source_manager);
352 out.parse_result().expect("invalid result")
353 }
354
355 pub fn dependency_resolver_mut(&mut self) -> &mut BTreeMap<Word, Arc<Library>> {
356 &mut self.dependency_resolver
357 }
358
359 pub fn register_library_dependency(&mut self, lib: Arc<Library>) {
361 let digest = *lib.digest();
362 self.dependency_resolver.insert(digest, lib);
363 }
364}
365
366#[derive(Debug, thiserror::Error)]
367enum PrintLnError {
368 #[error("address should fit in u32")]
369 InvalidAddress,
370 #[error("string length should fit in usize")]
371 InvalidLength,
372 #[error("string length {requested} exceeds maximum {max}")]
373 LengthExceeded { requested: usize, max: usize },
374 #[error("memory is not initialized")]
375 MemoryNotInitialized,
376 #[error("failed to read memory: {0}")]
377 MemoryRead(#[from] super::trace::MemoryReadError),
378 #[error("invalid UTF-8")]
379 InvalidUtf8,
380}
381
382fn register_builtin_trace_handlers(
383 host: &mut DebuggerHost<dyn SourceManager>,
384 trace_events: Rc<RefCell<BTreeMap<RowIndex, TraceEvent>>>,
385) {
386 let frame_start_events = Rc::clone(&trace_events);
387 host.register_trace_handler(TraceEvent::FrameStart, move |process, event| {
388 frame_start_events.borrow_mut().insert(process.clock(), event);
389 });
390 let frame_end_events = Rc::clone(&trace_events);
391 host.register_trace_handler(TraceEvent::FrameEnd, move |process, event| {
392 frame_end_events.borrow_mut().insert(process.clock(), event);
393 });
394
395 host.register_trace_handler(TraceEvent::PrintLn, move |process, _event| {
396 match decode_println(process) {
397 Ok(content) => {
398 log::log!(target: "stdout", Level::Info, "{content}");
399 }
400 Err(err) => {
401 log::warn!(
402 target: "executor",
403 "trace.{TRACE_PRINT_LN} failed at cycle {}: {err}",
404 process.clock(),
405 );
406 }
407 }
408 });
409
410 let assertion_events = Rc::clone(&trace_events);
411 host.register_assert_failed_tracer(move |process, event| {
412 assertion_events.borrow_mut().insert(process.clock(), event);
413 });
414}
415
416fn decode_println(process: &ProcessorState<'_>) -> Result<String, PrintLnError> {
421 let addr = u32::try_from(process.get_stack_item(0).as_canonical_u64())
422 .map_err(|_| PrintLnError::InvalidAddress)?;
423 let len = usize::try_from(process.get_stack_item(1).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
496struct 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}