midenc_debug/exec/
state.rs

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