1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use std::collections::{BTreeSet, VecDeque};

use miden_core::Word;
use miden_processor::{
    ContextId, ExecutionError, Operation, RowIndex, StackOutputs, VmState, VmStateIterator,
};

use super::ExecutionTrace;
use crate::{CallFrame, CallStack, TestFelt};

/// A special version of [crate::Executor] which provides finer-grained control over execution,
/// and captures a ton of information about the program being executed, so as to make it possible
/// to introspect everything about the program and the state of the VM at a given cycle.
///
/// This is used by the debugger to execute programs, and provide all of the functionality made
/// available by the TUI.
pub struct DebugExecutor {
    /// The underlying [VmStateIterator] being driven
    pub iter: VmStateIterator,
    /// The final outcome of the program being executed
    pub result: Result<StackOutputs, ExecutionError>,
    /// The set of contexts allocated during execution so far
    pub contexts: BTreeSet<ContextId>,
    /// The root context
    pub root_context: ContextId,
    /// The current context at `cycle`
    pub current_context: ContextId,
    /// The current call stack
    pub callstack: CallStack,
    /// A sliding window of the last 5 operations successfully executed by the VM
    pub recent: VecDeque<Operation>,
    /// The most recent [VmState] produced by the [VmStateIterator]
    pub last: Option<VmState>,
    /// The current clock cycle
    pub cycle: usize,
    /// Whether or not execution has terminated
    pub stopped: bool,
}

impl DebugExecutor {
    /// Advance the program state by one cycle.
    ///
    /// If the program has already reached its termination state, it returns the same result
    /// as the previous time it was called.
    ///
    /// Returns the call frame exited this cycle, if any
    pub fn step(&mut self) -> Result<Option<CallFrame>, ExecutionError> {
        if self.stopped {
            return self.result.as_ref().map(|_| None).map_err(|err| err.clone());
        }
        match self.iter.next() {
            Some(Ok(state)) => {
                self.cycle += 1;
                if self.current_context != state.ctx {
                    self.contexts.insert(state.ctx);
                    self.current_context = state.ctx;
                }

                if let Some(op) = state.op {
                    if self.recent.len() == 5 {
                        self.recent.pop_front();
                    }
                    self.recent.push_back(op);
                }

                let exited = self.callstack.next(&state);

                self.last = Some(state);

                Ok(exited)
            }
            Some(Err(err)) => {
                self.stopped = true;
                Err(err)
            }
            None => {
                self.stopped = true;
                Ok(None)
            }
        }
    }

    /// Consume the [DebugExecutor], converting it into an [ExecutionTrace] at the current cycle.
    pub fn into_execution_trace(self) -> ExecutionTrace {
        let last_cycle = self.cycle;
        let (_, _, _, chiplets, _) = self.iter.into_parts();
        let outputs = self
            .result
            .map(|res| res.stack().iter().copied().map(TestFelt).collect::<VecDeque<_>>())
            .unwrap_or_default();
        ExecutionTrace {
            root_context: self.root_context,
            last_cycle: RowIndex::from(last_cycle),
            chiplets: Chiplets::new(move |context, clk| chiplets.get_mem_state_at(context, clk)),
            outputs,
        }
    }
}
impl core::iter::FusedIterator for DebugExecutor {}
impl Iterator for DebugExecutor {
    type Item = Result<VmState, ExecutionError>;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        if self.stopped {
            return None;
        }
        match self.step() {
            Ok(_) => self.last.clone().map(Ok),
            Err(err) => Some(Err(err)),
        }
    }
}

// Dirty, gross, horrible hack until miden_processor::chiplets::Chiplets is exported
#[allow(clippy::type_complexity)]
pub struct Chiplets(Box<dyn Fn(ContextId, RowIndex) -> Vec<(u64, Word)>>);
impl Chiplets {
    pub fn new<F>(callback: F) -> Self
    where
        F: Fn(ContextId, RowIndex) -> Vec<(u64, Word)> + 'static,
    {
        Self(Box::new(callback))
    }

    pub fn get_mem_state_at(&self, context: ContextId, clk: RowIndex) -> Vec<(u64, Word)> {
        (self.0)(context, clk)
    }
}