1use std::{
2 cell::{Cell, RefCell},
3 collections::{BTreeMap, VecDeque},
4 fmt,
5 ops::Deref,
6 rc::Rc,
7 sync::{Arc, Mutex},
8};
9
10use log::Level;
11use miden_assembly_syntax::diagnostics::Report;
12use miden_core::{operations::DebugVarInfo, program::StackInputs};
13use miden_debug_types::{SourceManager, SourceManagerExt};
14use miden_mast_package::Package;
15use miden_package_registry::PackageCache;
16use miden_processor::{
17 ContextId, ExecutionError, ExecutionOptions, FastProcessor, Felt, LoadedMastForest,
18 ProcessorState,
19 advice::{AdviceInputs, AdviceMutation},
20 event::{EventError, EventHandler, EventName},
21 trace::RowIndex,
22};
23
24use super::{
25 DebugExecutor, DebuggerHost, Event, ExecutionConfig, ExecutionTrace,
26 event::{FRAME_END_EVENT, FRAME_START_EVENT, PRINTLN_EVENT},
27 query::read_memory_bytes,
28};
29use crate::{
30 HybridPackageRegistry,
31 debug::{CallStack, DebugVarTracker, NativePtr},
32 felt::FromMidenRepr,
33 profiling::{Profiler, ProfilerConfig},
34};
35
36const MAX_PRINTLN_BYTES: usize = 512 * 1024;
43
44pub struct Executor {
51 stack: StackInputs,
52 advice: AdviceInputs,
53 options: ExecutionOptions,
54 event_handlers: Vec<(EventName, Arc<dyn EventHandler>)>,
55 registry: HybridPackageRegistry,
56 record_event_mutations: bool,
57 profiler_config: ProfilerConfig,
58}
59
60impl Executor {
61 pub fn new(args: Vec<Felt>) -> Self {
63 let config = ExecutionConfig {
64 inputs: StackInputs::new(&args).expect("invalid stack inputs"),
65 ..Default::default()
66 };
67
68 Self::from_config(config)
69 }
70
71 pub fn from_config(config: ExecutionConfig) -> Self {
75 let ExecutionConfig {
76 inputs,
77 advice_inputs,
78 options,
79 } = config;
80
81 Self {
82 stack: inputs,
83 advice: advice_inputs,
84 options,
85 event_handlers: Default::default(),
86 registry: HybridPackageRegistry::empty(),
87 record_event_mutations: false,
88 profiler_config: Default::default(),
89 }
90 }
91
92 #[inline]
93 pub fn with_registry(mut self, registry: HybridPackageRegistry) -> Self {
94 self.registry = registry;
95 self
96 }
97
98 pub fn with_advice_inputs(&mut self, advice: AdviceInputs) -> &mut Self {
100 self.advice.extend(advice);
101 self
102 }
103
104 pub fn with_package(&mut self, package: Arc<Package>) -> Result<&mut Self, Report> {
106 self.registry.cache_package(package)?;
107 Ok(self)
108 }
109
110 pub fn with_event_advice_mutations_recording(&mut self) -> &mut Self {
121 self.record_event_mutations = true;
122 self
123 }
124
125 pub fn register_event_handler(
127 &mut self,
128 event: EventName,
129 handler: Arc<dyn EventHandler>,
130 ) -> Result<&mut Self, ExecutionError> {
131 self.event_handlers.push((event, handler));
132 Ok(self)
133 }
134
135 pub fn with_profiler_config(&mut self, profiler_config: ProfilerConfig) -> &mut Self {
137 self.profiler_config = profiler_config;
138 self
139 }
140
141 pub fn into_debug(
144 mut self,
145 package: Arc<Package>,
146 source_manager: Arc<dyn SourceManager>,
147 ) -> DebugExecutor {
148 assert!(package.is_program());
149
150 log::debug!("creating debug executor");
151
152 let mut host = DebuggerHost::new(source_manager.clone());
153 for lib in self.registry.all() {
154 host.load_package(lib);
155 }
156 for (event, handler) in core::mem::take(&mut self.event_handlers) {
157 host.register_event_handler(event, handler)
158 .expect("failed to register debug executor event handler");
159 }
160 if self.record_event_mutations {
161 host = host.with_event_advice_mutations_recording();
162 }
163
164 let events: Arc<Mutex<BTreeMap<RowIndex, Event>>> = Arc::new(Default::default());
165 register_builtin_event_handlers(&mut host, Arc::clone(&events));
166
167 let debug_var_events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>> =
169 Rc::new(Default::default());
170
171 let mut processor = FastProcessor::new_with_options(self.stack, self.advice, self.options)
172 .expect("advice inputs should fit advice map limits");
173
174 let root_context = ContextId::root();
175 let resume_ctx = processor
176 .get_initial_resume_context_for_package(package)
177 .expect("failed to get initial resume context");
178
179 let callstack = CallStack::new(events);
180 let debug_vars = DebugVarTracker::new(debug_var_events);
181 DebugExecutor {
182 processor,
183 host,
184 resume_ctx: Some(resume_ctx),
185 current_stack: vec![],
186 current_op: None,
187 current_asmop: None,
188 stack_outputs: Default::default(),
189 contexts: Default::default(),
190 root_context,
191 current_context: root_context,
192 callstack,
193 current_proc: None,
194 debug_vars,
195 last_debug_var_count: 0,
196 recent: VecDeque::with_capacity(5),
197 cycle: 0,
198 stopped: false,
199 profiler: Profiler::from_config(self.profiler_config),
200 }
201 }
202
203 pub fn into_debug_with_replay(
212 self,
213 package: Arc<Package>,
214 source_manager: Arc<dyn SourceManager>,
215 extra_mast_forests: Vec<LoadedMastForest>,
216 event_replay: VecDeque<Vec<AdviceMutation>>,
217 ) -> DebugExecutor {
218 assert!(package.is_program());
219
220 log::debug!("creating debug executor with event replay");
221
222 let mut host = DebuggerHost::new(source_manager.clone());
223 for lib in self.registry.all() {
224 host.load_package(lib);
225 }
226 for forest in extra_mast_forests {
227 host.load_mast_forest(forest);
228 }
229 host.set_event_replay(event_replay);
230
231 let debug_var_events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>> =
232 Rc::new(Default::default());
233
234 let events: Arc<Mutex<BTreeMap<RowIndex, Event>>> = Arc::new(Default::default());
235 register_builtin_event_handlers(&mut host, Arc::clone(&events));
236
237 let mut processor = FastProcessor::new_with_options(self.stack, self.advice, self.options)
238 .expect("advice inputs should fit advice map limits");
239
240 let root_context = ContextId::root();
241 let resume_ctx = processor
242 .get_initial_resume_context_for_package(package)
243 .expect("failed to get initial resume context");
244
245 let callstack = CallStack::new(events);
246 let debug_vars = DebugVarTracker::new(debug_var_events);
247 DebugExecutor {
248 processor,
249 host,
250 resume_ctx: Some(resume_ctx),
251 current_stack: vec![],
252 current_op: None,
253 current_asmop: None,
254 stack_outputs: Default::default(),
255 contexts: Default::default(),
256 root_context,
257 current_context: root_context,
258 callstack,
259 current_proc: None,
260 debug_vars,
261 last_debug_var_count: 0,
262 recent: VecDeque::with_capacity(5),
263 cycle: 0,
264 stopped: false,
265 profiler: Profiler::from_config(self.profiler_config),
266 }
267 }
268
269 pub fn capture_trace(
271 self,
272 package: Arc<Package>,
273 source_manager: Arc<dyn SourceManager>,
274 ) -> ExecutionTrace {
275 let mut executor = self.into_debug(package, source_manager);
276 loop {
277 if executor.stopped {
278 break;
279 }
280 match executor.step() {
281 Ok(_) => continue,
282 Err(err) => {
283 log::warn!(
284 target: "executor",
285 "capture_trace stopped early at cycle {}: {err}",
286 executor.cycle,
287 );
288 break;
289 }
290 }
291 }
292 executor.into_execution_trace()
293 }
294
295 #[track_caller]
297 pub fn execute(
298 self,
299 package: Arc<Package>,
300 source_manager: Arc<dyn SourceManager>,
301 ) -> ExecutionTrace {
302 let mut executor = self.into_debug(package, source_manager.clone());
303 loop {
304 if executor.stopped {
305 break;
306 }
307 match executor.step() {
308 Ok(_) => {
309 if log::log_enabled!(target: "executor", log::Level::Trace)
310 && let (Some(op), Some(asmop)) =
311 (executor.current_op, executor.current_asmop.as_ref())
312 {
313 log::trace!(target: "executor", "stack: {:?}", executor.current_stack);
314 let source_loc = asmop.location().map(|loc| {
315 let path = std::path::Path::new(loc.uri().path());
316 let file = source_manager.load_file(path).unwrap();
317 (file, loc.start)
318 });
319 if let Some((source_file, line_start)) = source_loc {
320 let line_number = source_file.content().line_index(line_start).number();
321 log::trace!(target: "executor", "in {} (located at {}:{})", asmop.context_name(), source_file.deref().uri().as_str(), line_number);
322 } else {
323 log::trace!(target: "executor", "in {} (no source location available)", asmop.context_name());
324 }
325 log::trace!(target: "executor", " executed `{op:?}` of `{}` ({} cycles)", asmop.op(), asmop.num_cycles());
326 log::trace!(target: "executor", " stack state: {:#?}", executor.current_stack);
327 }
328 }
329 Err(err) => {
330 render_execution_error(err, &executor, &source_manager);
331 }
332 }
333 }
334
335 executor.into_execution_trace()
336 }
337
338 pub fn execute_into<T>(self, package: Arc<Package>, source_manager: Arc<dyn SourceManager>) -> T
340 where
341 T: FromMidenRepr + PartialEq,
342 {
343 let out = self.execute(package, source_manager);
344 out.parse_result().expect("invalid result")
345 }
346}
347
348#[derive(Debug, thiserror::Error)]
349enum PrintLnError {
350 #[error("address should fit in u32")]
351 InvalidAddress,
352 #[error("string length should fit in usize")]
353 InvalidLength,
354 #[error("string length {requested} exceeds maximum {max}")]
355 LengthExceeded { requested: usize, max: usize },
356 #[error("memory is not initialized")]
357 MemoryNotInitialized,
358 #[error("failed to read memory: {0}")]
359 MemoryRead(#[from] super::trace::MemoryReadError),
360 #[error("invalid UTF-8")]
361 InvalidUtf8,
362}
363
364fn register_builtin_event_handlers(
365 host: &mut DebuggerHost<dyn SourceManager>,
366 events: Arc<Mutex<BTreeMap<RowIndex, Event>>>,
367) {
368 let println_handler = |process: &ProcessorState| -> Result<Vec<AdviceMutation>, EventError> {
369 match decode_println(process) {
370 Ok(content) => {
371 log::log!(target: "stdout", Level::Info, "{content}");
372 }
373 Err(err) => {
374 log::warn!(
375 target: "executor",
376 "emit.{PRINTLN_EVENT} failed at cycle {}: {err}",
377 process.clock(),
378 );
379 }
380 }
381
382 Ok(vec![])
383 };
384
385 host.register_event_handler(PRINTLN_EVENT, Arc::new(println_handler))
386 .expect("failed to register println event handler");
387
388 let frame_start_events = Arc::clone(&events);
389 let frame_start_handler =
390 move |process: &ProcessorState| -> Result<Vec<AdviceMutation>, EventError> {
391 frame_start_events.lock().unwrap().insert(process.clock(), Event::FrameStart);
392 Ok(vec![])
393 };
394 host.register_event_handler(FRAME_START_EVENT, Arc::new(frame_start_handler))
395 .expect("failed to register frame start event handler");
396
397 let frame_end_events = Arc::clone(&events);
398 let frame_end_handler =
399 move |process: &ProcessorState| -> Result<Vec<AdviceMutation>, EventError> {
400 frame_end_events.lock().unwrap().insert(process.clock(), Event::FrameEnd);
401 Ok(vec![])
402 };
403 host.register_event_handler(FRAME_END_EVENT, Arc::from(frame_end_handler))
404 .expect("failed to register frame end event handler");
405
406 }
413
414fn decode_println(process: &ProcessorState<'_>) -> Result<String, PrintLnError> {
419 let addr = u32::try_from(process.get_stack_item(1).as_canonical_u64())
420 .map_err(|_| PrintLnError::InvalidAddress)?;
421 let len = usize::try_from(process.get_stack_item(2).as_canonical_u64())
422 .map_err(|_| PrintLnError::InvalidLength)?;
423 if len > MAX_PRINTLN_BYTES {
424 return Err(PrintLnError::LengthExceeded {
425 requested: len,
426 max: MAX_PRINTLN_BYTES,
427 });
428 }
429 let ptr = NativePtr::from_ptr(addr);
430 let ctx = process.ctx();
431
432 let bytes = read_memory_bytes(ptr, len, |addr| {
433 process.get_mem_value(ctx, addr).ok_or(PrintLnError::MemoryNotInitialized)
434 })?;
435
436 String::from_utf8(bytes).map_err(|_| PrintLnError::InvalidUtf8)
437}
438
439#[track_caller]
440fn render_execution_error(
441 err: ExecutionError,
442 execution_state: &DebugExecutor,
443 source_manager: &dyn SourceManager,
444) -> ! {
445 use miden_assembly_syntax::diagnostics::{
446 LabeledSpan, miette::miette, reporting::PrintDiagnostic,
447 };
448
449 let stacktrace = execution_state.callstack.stacktrace(&execution_state.recent, source_manager);
450
451 eprintln!("{stacktrace}");
452
453 if !execution_state.current_stack.is_empty() {
454 let stack = execution_state.current_stack.iter().map(|elem| elem.as_canonical_u64());
455 let stack = DisplayValues::new(stack);
456 eprintln!(
457 "\nLast Known State (at most recent instruction which succeeded):
458 | Operand Stack: [{stack}]
459 "
460 );
461
462 let mut labels = vec![];
463 if let Some(span) = stacktrace
464 .current_frame()
465 .and_then(|frame| frame.location.as_ref())
466 .map(|loc| loc.span)
467 {
468 labels.push(LabeledSpan::new_with_span(
469 None,
470 span.start().to_usize()..span.end().to_usize(),
471 ));
472 }
473 let report = miette!(
474 labels = labels,
475 "program execution failed at step {step} (cycle {cycle}): {err}",
476 step = execution_state.cycle,
477 cycle = execution_state.cycle,
478 );
479 let report = match stacktrace
480 .current_frame()
481 .and_then(|frame| frame.location.as_ref())
482 .map(|loc| loc.source_file.clone())
483 {
484 Some(source) => report.with_source_code(source),
485 None => report,
486 };
487
488 panic!("{}", PrintDiagnostic::new(report));
489 } else {
490 panic!("program execution failed at step {step}: {err}", step = execution_state.cycle);
491 }
492}
493
494struct DisplayValues<T>(Cell<Option<T>>);
496
497impl<T> DisplayValues<T> {
498 pub fn new(inner: T) -> Self {
499 Self(Cell::new(Some(inner)))
500 }
501}
502
503impl<T, I> fmt::Display for DisplayValues<I>
504where
505 T: fmt::Display,
506 I: Iterator<Item = T>,
507{
508 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
509 let iter = self.0.take().unwrap();
510 for (i, item) in iter.enumerate() {
511 if i == 0 {
512 write!(f, "{item}")?;
513 } else {
514 write!(f, ", {item}")?;
515 }
516 }
517 Ok(())
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
528 fn records_event_mutations_and_replays_them() {
529 use std::sync::atomic::{AtomicU64, Ordering};
530
531 use miden_assembly::DefaultSourceManager;
532 use miden_core::events::EventId;
533 use miden_processor::{ProcessorState, advice::AdviceMutation, event::EventError};
534
535 struct CountingHandler {
536 calls: AtomicU64,
537 }
538
539 impl EventHandler for CountingHandler {
540 fn on_event(
541 &self,
542 _process: &ProcessorState<'_>,
543 ) -> Result<Vec<AdviceMutation>, EventError> {
544 let call = self.calls.fetch_add(1, Ordering::SeqCst);
545 Ok(vec![AdviceMutation::ExtendStack {
546 values: vec![Felt::from(100u32 + call as u32)],
547 }])
548 }
549 }
550
551 let source_manager: Arc<DefaultSourceManager> = Arc::new(DefaultSourceManager::default());
552 let event_name = "miden-debug::test::record-replay";
553 let event_id = EventId::from_name(event_name).as_u64();
554 let source = format!(
557 "begin push.{event_id} emit drop adv_push push.{event_id} emit drop adv_push add swap \
558 drop end"
559 );
560 let program = miden_assembly::Assembler::new(source_manager.clone())
561 .assemble_program("program", source)
562 .map(Arc::from)
563 .expect("failed to assemble test program");
564
565 let mut executor = Executor::new(Vec::new());
566 executor
567 .register_event_handler(
568 EventName::from_string(event_name.to_string()),
569 Arc::new(CountingHandler {
570 calls: AtomicU64::new(0),
571 }),
572 )
573 .expect("failed to register event handler");
574 executor.with_event_advice_mutations_recording();
575
576 let mut debug_executor = executor.into_debug(Arc::clone(&program), source_manager.clone());
579 while !debug_executor.stopped {
580 debug_executor.step().expect("recording step failed");
581 }
582 let recorded = debug_executor.host.take_recorded_event_mutations();
583 let recorded_result: u32 =
584 debug_executor.into_execution_trace().parse_result().expect("invalid result");
585 assert_eq!(recorded_result, 201);
586
587 assert_eq!(recorded.len(), 2, "expected one recorded entry per emit");
588 for (index, batch) in recorded.iter().enumerate() {
589 match batch.as_slice() {
590 [AdviceMutation::ExtendStack { values }] => {
591 assert_eq!(values.as_slice(), &[Felt::from(100u32 + index as u32)]);
592 }
593 _ => panic!("unexpected mutations recorded for event {index}"),
594 }
595 }
596
597 let replay_executor = Executor::new(Vec::new());
600 let mut debug_executor = replay_executor.into_debug_with_replay(
601 program,
602 source_manager,
603 Vec::new(),
604 recorded.into(),
605 );
606 while !debug_executor.stopped {
607 debug_executor.step().expect("replay step failed");
608 }
609 let replayed_result: u32 = debug_executor
610 .into_execution_trace()
611 .parse_result()
612 .expect("invalid replay result");
613 assert_eq!(replayed_result, recorded_result);
614 }
615
616 #[test]
620 fn replays_from_a_serialized_snapshot() {
621 use std::sync::atomic::{AtomicU64, Ordering};
622
623 use miden_assembly::DefaultSourceManager;
624 use miden_core::events::EventId;
625 use miden_processor::{ProcessorState, advice::AdviceMutation, event::EventError};
626
627 use crate::exec::ReplaySnapshot;
628
629 struct CountingHandler {
630 calls: AtomicU64,
631 }
632
633 impl EventHandler for CountingHandler {
634 fn on_event(
635 &self,
636 _process: &ProcessorState<'_>,
637 ) -> Result<Vec<AdviceMutation>, EventError> {
638 let call = self.calls.fetch_add(1, Ordering::SeqCst);
639 Ok(vec![AdviceMutation::ExtendStack {
640 values: vec![Felt::from(100u32 + call as u32)],
641 }])
642 }
643 }
644
645 let source_manager: Arc<DefaultSourceManager> = Arc::new(DefaultSourceManager::default());
646 let event_name = "miden-debug::test::snapshot-replay";
647 let event_id = EventId::from_name(event_name).as_u64();
648 let source = format!(
649 "begin push.{event_id} emit drop adv_push push.{event_id} emit drop adv_push add add \
650 end"
651 );
652 let program = miden_assembly::Assembler::new(source_manager.clone())
653 .assemble_program("program", source)
654 .map(Arc::<Package>::from)
655 .expect("failed to assemble test program");
656 let stack_inputs = StackInputs::new(&[Felt::from(7u32)]).unwrap();
657 let advice_inputs = AdviceInputs::default();
658 let options = ExecutionOptions::default();
659
660 let mut executor = Executor::from_config(ExecutionConfig {
662 inputs: stack_inputs,
663 advice_inputs: advice_inputs.clone(),
664 options,
665 });
666 executor
667 .register_event_handler(
668 EventName::from_string(event_name.to_string()),
669 Arc::new(CountingHandler {
670 calls: AtomicU64::new(0),
671 }),
672 )
673 .expect("failed to register event handler");
674 executor.with_event_advice_mutations_recording();
675 let mut debug_executor = executor.into_debug(program.clone(), source_manager.clone());
676 while !debug_executor.stopped {
677 debug_executor.step().expect("recording step failed");
678 }
679 let event_log = debug_executor.host.take_recorded_event_mutations();
680 let recorded_result: u32 =
681 debug_executor.into_execution_trace().parse_result().expect("invalid result");
682
683 let snapshot = ReplaySnapshot {
685 package: program.clone(),
686 stack_inputs,
687 advice_inputs,
688 options,
689 mast_forests: vec![LoadedMastForest::with_package_debug_info(
690 program.mast_forest().clone(),
691 program.debug_info(),
692 )],
693 event_log,
694 };
695 let restored = ReplaySnapshot::read_from_bytes(&snapshot.to_bytes())
696 .expect("snapshot failed to deserialize");
697
698 let replay_executor = Executor::from_config(ExecutionConfig {
700 inputs: restored.stack_inputs,
701 advice_inputs: restored.advice_inputs,
702 options: restored.options,
703 });
704 let mut debug_executor = replay_executor.into_debug_with_replay(
705 restored.package.clone(),
706 source_manager,
707 restored.mast_forests.clone(),
708 restored.event_log.into(),
709 );
710 while !debug_executor.stopped {
711 debug_executor.step().expect("replay step failed");
712 }
713 let replayed_result: u32 = debug_executor
714 .into_execution_trace()
715 .parse_result()
716 .expect("invalid replay result");
717 assert_eq!(replayed_result, recorded_result);
718 assert_eq!(replayed_result, 208);
719 }
720}