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)
}
}