miden_debug_engine/exec/
diagnostic.rs1use std::{sync::Arc, vec::Vec};
2
3use miden_core::{Word, events::EventId, program::Program};
4use miden_processor::{
5 BaseHost, ExecutionError, ExecutionOptions, ExecutionOutput, FastProcessor, Felt,
6 FutureMaybeSend, Host, LoadedMastForest, ProcessorState, StackInputs,
7 advice::{AdviceInputs, AdviceMutation},
8 event::EventError,
9 trace::RowIndex,
10};
11
12struct DiagnosticHostWrapper<'a, H: Host> {
21 inner: &'a mut H,
22 call_depth: usize,
24 last_stack_state: Vec<Felt>,
26 last_cycle: RowIndex,
28}
29
30impl<'a, H: Host> DiagnosticHostWrapper<'a, H> {
31 fn new(inner: &'a mut H) -> Self {
32 Self {
33 inner,
34 call_depth: 0,
35 last_stack_state: Vec::new(),
36 last_cycle: RowIndex::from(0u32),
37 }
38 }
39
40 fn report_diagnostics(&self, err: &ExecutionError) {
42 eprintln!("\n=== Transaction Execution Failed ===");
43 eprintln!("Error: {err}");
44 eprintln!("Last known cycle: {}", self.last_cycle);
45 eprintln!("Call depth at failure: {}", self.call_depth);
46
47 if !self.last_stack_state.is_empty() {
48 let stack_display: Vec<_> =
49 self.last_stack_state.iter().take(16).map(|f| f.as_canonical_u64()).collect();
50 eprintln!("Last known stack state (top 16): {stack_display:?}");
51 }
52
53 eprintln!("====================================\n");
54 }
55
56 fn capture_state(&mut self, process: &ProcessorState<'_>) {
57 self.last_stack_state = process.get_stack_state();
58 self.last_cycle = process.clock();
59 }
60}
61
62impl<H: Host> BaseHost for DiagnosticHostWrapper<'_, H> {
63 fn get_label_and_source_file(
64 &self,
65 location: &miden_debug_types::Location,
66 ) -> (miden_debug_types::SourceSpan, Option<Arc<miden_debug_types::SourceFile>>) {
67 self.inner.get_label_and_source_file(location)
68 }
69
70 fn resolve_event(
71 &self,
72 event_id: miden_core::events::EventId,
73 ) -> Option<&miden_core::events::EventName> {
74 self.inner.resolve_event(event_id)
75 }
76}
77
78impl<H: Host> Host for DiagnosticHostWrapper<'_, H> {
79 fn get_mast_forest(
80 &self,
81 node_digest: &Word,
82 ) -> impl FutureMaybeSend<Option<LoadedMastForest>> {
83 self.inner.get_mast_forest(node_digest)
84 }
85
86 fn on_event(
87 &mut self,
88 process: &ProcessorState<'_>,
89 ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
90 self.capture_state(process);
91 let event_id = EventId::from_felt(process.get_stack_item(0));
92 match crate::Event::from(event_id) {
93 crate::Event::FrameStart => self.call_depth += 1,
94 crate::Event::FrameEnd => self.call_depth = self.call_depth.saturating_sub(1),
95 _ => (),
96 }
97 self.inner.on_event(process)
98 }
99}
100
101pub struct DiagnosticExecutor {
126 stack_inputs: StackInputs,
127 advice_inputs: AdviceInputs,
128 options: ExecutionOptions,
129}
130
131impl DiagnosticExecutor {
132 pub fn new(
133 stack_inputs: StackInputs,
134 advice_inputs: AdviceInputs,
135 options: ExecutionOptions,
136 ) -> Self {
137 DiagnosticExecutor {
138 stack_inputs,
139 advice_inputs,
140 options,
141 }
142 }
143
144 pub fn execute_async<H: Host + Send>(
145 self,
146 program: &Program,
147 host: &mut H,
148 ) -> impl FutureMaybeSend<Result<ExecutionOutput, ExecutionError>> {
149 async move {
150 let processor = FastProcessor::new_with_options(
152 self.stack_inputs,
153 self.advice_inputs,
154 self.options,
155 )
156 .expect("advice inputs should fit advice map limits");
157
158 let mut wrapper = DiagnosticHostWrapper::new(host);
159
160 match processor.execute(program, &mut wrapper).await {
161 Ok(output) => Ok(output),
162 Err(err) => {
163 wrapper.report_diagnostics(&err);
164 Err(err)
165 }
166 }
167 }
168 }
169}