miden_debug_engine/exec/
diagnostic.rs1use std::{sync::Arc, vec::Vec};
2
3use miden_core::{Word, operations::DebugOptions, program::Program};
4use miden_processor::{
5 BaseHost, ExecutionError, ExecutionOptions, ExecutionOutput, FastProcessor, Felt,
6 FutureMaybeSend, Host, ProcessorState, StackInputs, TraceError,
7 advice::{AdviceInputs, AdviceMutation},
8 event::EventError,
9 mast::MastForest,
10 trace::RowIndex,
11};
12
13use super::TraceEvent;
14
15struct DiagnosticHostWrapper<'a, H: Host> {
24 inner: &'a mut H,
25 call_depth: usize,
27 last_stack_state: Vec<Felt>,
29 last_cycle: RowIndex,
31}
32
33impl<'a, H: Host> DiagnosticHostWrapper<'a, H> {
34 fn new(inner: &'a mut H) -> Self {
35 Self {
36 inner,
37 call_depth: 0,
38 last_stack_state: Vec::new(),
39 last_cycle: RowIndex::from(0u32),
40 }
41 }
42
43 fn report_diagnostics(&self, err: &ExecutionError) {
45 eprintln!("\n=== Transaction Execution Failed ===");
46 eprintln!("Error: {err}");
47 eprintln!("Last known cycle: {}", self.last_cycle);
48 eprintln!("Call depth at failure: {}", self.call_depth);
49
50 if !self.last_stack_state.is_empty() {
51 let stack_display: Vec<_> =
52 self.last_stack_state.iter().take(16).map(|f| f.as_canonical_u64()).collect();
53 eprintln!("Last known stack state (top 16): {stack_display:?}");
54 }
55
56 eprintln!("====================================\n");
57 }
58
59 fn capture_state(&mut self, process: &ProcessorState<'_>) {
60 self.last_stack_state = process.get_stack_state();
61 self.last_cycle = process.clock();
62 }
63}
64
65impl<H: Host> BaseHost for DiagnosticHostWrapper<'_, H> {
66 fn get_label_and_source_file(
67 &self,
68 location: &miden_debug_types::Location,
69 ) -> (miden_debug_types::SourceSpan, Option<Arc<miden_debug_types::SourceFile>>) {
70 self.inner.get_label_and_source_file(location)
71 }
72
73 fn on_debug(
74 &mut self,
75 process: &ProcessorState<'_>,
76 options: &DebugOptions,
77 ) -> Result<(), miden_processor::DebugError> {
78 self.inner.on_debug(process, options)
79 }
80
81 fn on_trace(&mut self, process: &ProcessorState<'_>, trace_id: u32) -> Result<(), TraceError> {
82 self.capture_state(process);
83
84 let event = TraceEvent::from(trace_id);
85 match event {
86 TraceEvent::FrameStart => self.call_depth += 1,
87 TraceEvent::FrameEnd => self.call_depth = self.call_depth.saturating_sub(1),
88 _ => {}
89 }
90
91 self.inner.on_trace(process, trace_id)
92 }
93
94 fn resolve_event(
95 &self,
96 event_id: miden_core::events::EventId,
97 ) -> Option<&miden_core::events::EventName> {
98 self.inner.resolve_event(event_id)
99 }
100}
101
102impl<H: Host> Host for DiagnosticHostWrapper<'_, H> {
103 fn get_mast_forest(&self, node_digest: &Word) -> impl FutureMaybeSend<Option<Arc<MastForest>>> {
104 self.inner.get_mast_forest(node_digest)
105 }
106
107 fn on_event(
108 &mut self,
109 process: &ProcessorState<'_>,
110 ) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
111 self.capture_state(process);
112 self.inner.on_event(process)
113 }
114}
115
116pub struct DiagnosticExecutor {
141 stack_inputs: StackInputs,
142 advice_inputs: AdviceInputs,
143 options: ExecutionOptions,
144}
145
146impl DiagnosticExecutor {
147 pub fn new(
148 stack_inputs: StackInputs,
149 advice_inputs: AdviceInputs,
150 options: ExecutionOptions,
151 ) -> Self {
152 DiagnosticExecutor {
153 stack_inputs,
154 advice_inputs,
155 options,
156 }
157 }
158
159 pub fn execute_async<H: Host + Send>(
160 self,
161 program: &Program,
162 host: &mut H,
163 ) -> impl FutureMaybeSend<Result<ExecutionOutput, ExecutionError>> {
164 async move {
165 let options = self.options.with_debugging(true).with_tracing(true);
167 let processor =
168 FastProcessor::new_with_options(self.stack_inputs, self.advice_inputs, options)
169 .expect("advice inputs should fit advice map limits");
170
171 let mut wrapper = DiagnosticHostWrapper::new(host);
172
173 match processor.execute(program, &mut wrapper).await {
174 Ok(output) => Ok(output),
175 Err(err) => {
176 wrapper.report_diagnostics(&err);
177 Err(err)
178 }
179 }
180 }
181 }
182}