miden_debug_engine/exec/
diagnostic.rs1use alloc::{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, TraceError},
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 #[cfg(feature = "std")]
42 fn report_diagnostics(&self, err: &ExecutionError) {
43 eprintln!("\n=== Transaction Execution Failed ===");
44 eprintln!("Error: {err}");
45 eprintln!("Last known cycle: {}", self.last_cycle);
46 eprintln!("Call depth at failure: {}", self.call_depth);
47
48 if !self.last_stack_state.is_empty() {
49 let stack_display: Vec<_> =
50 self.last_stack_state.iter().take(16).map(|f| f.as_canonical_u64()).collect();
51 eprintln!("Last known stack state (top 16): {stack_display:?}");
52 }
53
54 eprintln!("====================================\n");
55 }
56
57 #[cfg(not(feature = "std"))]
58 fn report_diagnostics(&self, _err: &ExecutionError) {}
59
60 fn capture_state(&mut self, process: &ProcessorState<'_>) {
61 self.last_stack_state = process.get_stack_state();
62 self.last_cycle = process.clock();
63 }
64}
65
66impl<H: Host> BaseHost for DiagnosticHostWrapper<'_, H> {
67 fn get_label_and_source_file(
68 &self,
69 location: &miden_debug_types::Location,
70 ) -> (miden_debug_types::SourceSpan, Option<Arc<miden_debug_types::SourceFile>>) {
71 self.inner.get_label_and_source_file(location)
72 }
73
74 fn resolve_event(
75 &self,
76 event_id: miden_core::events::EventId,
77 ) -> Option<&miden_core::events::EventName> {
78 self.inner.resolve_event(event_id)
79 }
80
81 fn resolve_trace(
82 &self,
83 trace_id: miden_core::events::EventId,
84 ) -> Option<&miden_core::events::EventName> {
85 self.inner.resolve_trace(trace_id)
86 }
87}
88
89impl<H: Host> Host for DiagnosticHostWrapper<'_, H> {
90 fn get_mast_forest(
91 &self,
92 node_digest: &Word,
93 ) -> impl FutureMaybeSend<Option<LoadedMastForest>> {
94 self.inner.get_mast_forest(node_digest)
95 }
96
97 fn on_event(
98 &mut self,
99 process: &ProcessorState<'_>,
100 ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
101 self.capture_state(process);
102 let event_id = EventId::from_felt(process.get_stack_item(0));
103 match crate::Event::from(event_id) {
104 crate::Event::FrameStart => self.call_depth += 1,
105 crate::Event::FrameEnd => self.call_depth = self.call_depth.saturating_sub(1),
106 _ => (),
107 }
108 self.inner.on_event(process)
109 }
110
111 fn on_trace(
112 &mut self,
113 process: &ProcessorState<'_>,
114 ) -> impl FutureMaybeSend<Result<(), TraceError>> {
115 self.capture_state(process);
116 self.inner.on_trace(process)
117 }
118}
119
120pub struct DiagnosticExecutor {
145 stack_inputs: StackInputs,
146 advice_inputs: AdviceInputs,
147 options: ExecutionOptions,
148}
149
150impl DiagnosticExecutor {
151 pub fn new(
152 stack_inputs: StackInputs,
153 advice_inputs: AdviceInputs,
154 options: ExecutionOptions,
155 ) -> Self {
156 DiagnosticExecutor {
157 stack_inputs,
158 advice_inputs,
159 options,
160 }
161 }
162
163 pub fn execute_async<H: Host + Send>(
164 self,
165 program: &Program,
166 host: &mut H,
167 ) -> impl FutureMaybeSend<Result<ExecutionOutput, ExecutionError>> {
168 async move {
169 let processor = FastProcessor::new_with_options(
171 self.stack_inputs,
172 self.advice_inputs,
173 self.options,
174 )
175 .expect("advice inputs should fit advice map limits");
176
177 let mut wrapper = DiagnosticHostWrapper::new(host);
178
179 match processor.execute(program, &mut wrapper).await {
180 Ok(output) => Ok(output),
181 Err(err) => {
182 wrapper.report_diagnostics(&err);
183 Err(err)
184 }
185 }
186 }
187 }
188}
189
190#[cfg(all(test, feature = "std"))]
191mod tests;