midenc_debug/exec/
state.rs

1use std::collections::{BTreeSet, VecDeque};
2
3use miden_core::Word;
4use miden_processor::{
5    ContextId, ExecutionError, Operation, RowIndex, StackOutputs, VmState, VmStateIterator,
6};
7
8use super::ExecutionTrace;
9use crate::{CallFrame, CallStack, TestFelt};
10
11/// A special version of [crate::Executor] which provides finer-grained control over execution,
12/// and captures a ton of information about the program being executed, so as to make it possible
13/// to introspect everything about the program and the state of the VM at a given cycle.
14///
15/// This is used by the debugger to execute programs, and provide all of the functionality made
16/// available by the TUI.
17pub struct DebugExecutor {
18    /// The underlying [VmStateIterator] being driven
19    pub iter: VmStateIterator,
20    /// The final outcome of the program being executed
21    pub result: Result<StackOutputs, ExecutionError>,
22    /// The set of contexts allocated during execution so far
23    pub contexts: BTreeSet<ContextId>,
24    /// The root context
25    pub root_context: ContextId,
26    /// The current context at `cycle`
27    pub current_context: ContextId,
28    /// The current call stack
29    pub callstack: CallStack,
30    /// A sliding window of the last 5 operations successfully executed by the VM
31    pub recent: VecDeque<Operation>,
32    /// The most recent [VmState] produced by the [VmStateIterator]
33    pub last: Option<VmState>,
34    /// The current clock cycle
35    pub cycle: usize,
36    /// Whether or not execution has terminated
37    pub stopped: bool,
38}
39
40impl DebugExecutor {
41    /// Advance the program state by one cycle.
42    ///
43    /// If the program has already reached its termination state, it returns the same result
44    /// as the previous time it was called.
45    ///
46    /// Returns the call frame exited this cycle, if any
47    pub fn step(&mut self) -> Result<Option<CallFrame>, ExecutionError> {
48        if self.stopped {
49            return self.result.as_ref().map(|_| None).map_err(|err| err.clone());
50        }
51        match self.iter.next() {
52            Some(Ok(state)) => {
53                self.cycle += 1;
54                if self.current_context != state.ctx {
55                    self.contexts.insert(state.ctx);
56                    self.current_context = state.ctx;
57                }
58
59                if let Some(op) = state.op {
60                    if self.recent.len() == 5 {
61                        self.recent.pop_front();
62                    }
63                    self.recent.push_back(op);
64                }
65
66                let exited = self.callstack.next(&state);
67
68                self.last = Some(state);
69
70                Ok(exited)
71            }
72            Some(Err(err)) => {
73                self.stopped = true;
74                Err(err)
75            }
76            None => {
77                self.stopped = true;
78                Ok(None)
79            }
80        }
81    }
82
83    /// Consume the [DebugExecutor], converting it into an [ExecutionTrace] at the current cycle.
84    pub fn into_execution_trace(self) -> ExecutionTrace {
85        let last_cycle = self.cycle;
86        let trace_len_summary = *self.iter.trace_len_summary();
87        let (_, _, _, chiplets, _) = self.iter.into_parts();
88        let outputs = self.result.unwrap_or_default();
89        ExecutionTrace {
90            root_context: self.root_context,
91            last_cycle: RowIndex::from(last_cycle),
92            chiplets: Chiplets::new(move |context, clk| chiplets.get_mem_state_at(context, clk)),
93            outputs,
94            trace_len_summary,
95        }
96    }
97}
98impl core::iter::FusedIterator for DebugExecutor {}
99impl Iterator for DebugExecutor {
100    type Item = Result<VmState, ExecutionError>;
101
102    #[inline]
103    fn next(&mut self) -> Option<Self::Item> {
104        if self.stopped {
105            return None;
106        }
107        match self.step() {
108            Ok(_) => self.last.clone().map(Ok),
109            Err(err) => Some(Err(err)),
110        }
111    }
112}
113
114// Dirty, gross, horrible hack until miden_processor::chiplets::Chiplets is exported
115#[allow(clippy::type_complexity)]
116pub struct Chiplets(Box<dyn Fn(ContextId, RowIndex) -> Vec<(u64, Word)>>);
117impl Chiplets {
118    pub fn new<F>(callback: F) -> Self
119    where
120        F: Fn(ContextId, RowIndex) -> Vec<(u64, Word)> + 'static,
121    {
122        Self(Box::new(callback))
123    }
124
125    pub fn get_mem_state_at(&self, context: ContextId, clk: RowIndex) -> Vec<(u64, Word)> {
126        (self.0)(context, clk)
127    }
128}