use std::{sync::Arc, vec::Vec};
use miden_core::{Word, operations::DebugOptions, program::Program};
use miden_processor::{
ExecutionError, ExecutionOptions, ExecutionOutput, FastProcessor, Felt, FutureMaybeSend, Host,
ProcessorState, StackInputs, TraceError,
advice::{AdviceInputs, AdviceMutation},
event::EventError,
mast::MastForest,
trace::RowIndex,
};
use super::{ProgramExecutor, TraceEvent};
struct DiagnosticHostWrapper<'a, H: Host> {
inner: &'a mut H,
call_depth: usize,
last_stack_state: Vec<Felt>,
last_cycle: RowIndex,
}
impl<'a, H: Host> DiagnosticHostWrapper<'a, H> {
fn new(inner: &'a mut H) -> Self {
Self {
inner,
call_depth: 0,
last_stack_state: Vec::new(),
last_cycle: RowIndex::from(0u32),
}
}
fn report_diagnostics(&self, err: &ExecutionError) {
eprintln!("\n=== Transaction Execution Failed ===");
eprintln!("Error: {err}");
eprintln!("Last known cycle: {}", self.last_cycle);
eprintln!("Call depth at failure: {}", self.call_depth);
if !self.last_stack_state.is_empty() {
let stack_display: Vec<_> =
self.last_stack_state.iter().take(16).map(|f| f.as_canonical_u64()).collect();
eprintln!("Last known stack state (top 16): {stack_display:?}");
}
eprintln!("====================================\n");
}
fn capture_state(&mut self, process: &ProcessorState<'_>) {
self.last_stack_state = process.get_stack_state();
self.last_cycle = process.clock();
}
}
impl<H: Host> Host for DiagnosticHostWrapper<'_, H> {
fn get_label_and_source_file(
&self,
location: &miden_debug_types::Location,
) -> (miden_debug_types::SourceSpan, Option<Arc<miden_debug_types::SourceFile>>) {
self.inner.get_label_and_source_file(location)
}
fn get_mast_forest(&self, node_digest: &Word) -> impl FutureMaybeSend<Option<Arc<MastForest>>> {
self.inner.get_mast_forest(node_digest)
}
fn on_event(
&mut self,
process: &ProcessorState<'_>,
) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
self.capture_state(process);
self.inner.on_event(process)
}
fn on_debug(
&mut self,
process: &ProcessorState<'_>,
options: &DebugOptions,
) -> Result<(), miden_processor::DebugError> {
self.inner.on_debug(process, options)
}
fn on_trace(&mut self, process: &ProcessorState<'_>, trace_id: u32) -> Result<(), TraceError> {
self.capture_state(process);
let event = TraceEvent::from(trace_id);
match event {
TraceEvent::FrameStart => self.call_depth += 1,
TraceEvent::FrameEnd => self.call_depth = self.call_depth.saturating_sub(1),
_ => {}
}
self.inner.on_trace(process, trace_id)
}
fn resolve_event(
&self,
event_id: miden_core::events::EventId,
) -> Option<&miden_core::events::EventName> {
self.inner.resolve_event(event_id)
}
}
pub struct DiagnosticExecutor {
stack_inputs: StackInputs,
advice_inputs: AdviceInputs,
options: ExecutionOptions,
}
impl ProgramExecutor for DiagnosticExecutor {
fn new(
stack_inputs: StackInputs,
advice_inputs: AdviceInputs,
options: ExecutionOptions,
) -> Self {
DiagnosticExecutor {
stack_inputs,
advice_inputs,
options,
}
}
fn execute<H: Host + Send>(
self,
program: &Program,
host: &mut H,
) -> impl FutureMaybeSend<Result<ExecutionOutput, ExecutionError>> {
async move {
let options = self.options.with_debugging(true).with_tracing(true);
let processor = FastProcessor::new(self.stack_inputs)
.with_advice(self.advice_inputs)
.with_options(options);
let mut wrapper = DiagnosticHostWrapper::new(host);
match processor.execute(program, &mut wrapper).await {
Ok(output) => Ok(output),
Err(err) => {
wrapper.report_diagnostics(&err);
Err(err)
}
}
}
}
}