miden_debug_engine/exec/
state.rs1use std::collections::{BTreeSet, VecDeque};
2
3use miden_core::{
4 mast::{MastNode, MastNodeId},
5 operations::AssemblyOp,
6};
7use miden_processor::{
8 ContextId, Continuation, ExecutionError, FastProcessor, Felt, ResumeContext, StackOutputs,
9 operation::Operation, trace::RowIndex,
10};
11
12use super::{DebuggerHost, ExecutionTrace};
13use crate::debug::{CallFrame, CallStack, ControlFlowOp, StepInfo};
14
15fn poll_immediately<T>(fut: impl std::future::Future<Output = T>) -> T {
22 let waker = std::task::Waker::noop();
23 let mut cx = std::task::Context::from_waker(waker);
24 let mut fut = std::pin::pin!(fut);
25 match fut.as_mut().poll(&mut cx) {
26 std::task::Poll::Ready(val) => val,
27 std::task::Poll::Pending => panic!("future was expected to complete immediately"),
28 }
29}
30
31pub struct DebugExecutor {
38 pub processor: FastProcessor,
40 pub host: DebuggerHost<dyn miden_assembly::SourceManager>,
42 pub resume_ctx: Option<ResumeContext>,
44
45 pub current_stack: Vec<Felt>,
48 pub current_op: Option<Operation>,
50 pub current_asmop: Option<AssemblyOp>,
52
53 pub stack_outputs: StackOutputs,
55 pub contexts: BTreeSet<ContextId>,
57 pub root_context: ContextId,
59 pub current_context: ContextId,
61 pub callstack: CallStack,
63 pub recent: VecDeque<Operation>,
65 pub cycle: usize,
67 pub stopped: bool,
69}
70
71pub(crate) fn extract_current_op(
74 ctx: &ResumeContext,
75) -> (Option<Operation>, Option<MastNodeId>, Option<usize>, Option<ControlFlowOp>) {
76 let forest = ctx.current_forest();
77 for cont in ctx.continuation_stack().iter_continuations_for_next_clock() {
78 match cont {
79 Continuation::ResumeBasicBlock {
80 node_id,
81 batch_index,
82 op_idx_in_batch,
83 } => {
84 let node = &forest[*node_id];
85 if let MastNode::Block(block) = node {
86 let mut global_idx = 0;
88 for batch in &block.op_batches()[..*batch_index] {
89 global_idx += batch.ops().len();
90 }
91 global_idx += op_idx_in_batch;
92 let op = block.op_batches()[*batch_index].ops().get(*op_idx_in_batch).copied();
93 return (op, Some(*node_id), Some(global_idx), None);
94 }
95 }
96 Continuation::Respan {
97 node_id,
98 batch_index,
99 } => {
100 let node = &forest[*node_id];
101 if let MastNode::Block(block) = node {
102 let mut global_idx = 0;
103 for batch in &block.op_batches()[..*batch_index] {
104 global_idx += batch.ops().len();
105 }
106 return (None, Some(*node_id), Some(global_idx), Some(ControlFlowOp::Respan));
107 }
108 }
109 Continuation::StartNode(node_id) => {
110 let control = match &forest[*node_id] {
111 MastNode::Block(_) => Some(ControlFlowOp::Span),
112 MastNode::Join(_) => Some(ControlFlowOp::Join),
113 MastNode::Split(_) => Some(ControlFlowOp::Split),
114 _ => None,
115 };
116 return (None, Some(*node_id), None, control);
117 }
118 Continuation::FinishBasicBlock(_)
119 | Continuation::FinishJoin(_)
120 | Continuation::FinishSplit(_)
121 | Continuation::FinishLoop { .. }
122 | Continuation::FinishCall(_)
123 | Continuation::FinishDyn(_)
124 | Continuation::FinishExternal(_) => {
125 return (None, None, None, Some(ControlFlowOp::End));
126 }
127 other if other.increments_clk() => {
128 return (None, None, None, None);
129 }
130 _ => continue,
131 }
132 }
133 (None, None, None, None)
134}
135
136impl DebugExecutor {
137 pub fn step(&mut self) -> Result<Option<CallFrame>, ExecutionError> {
144 if self.stopped {
145 return Ok(None);
146 }
147
148 let resume_ctx = match self.resume_ctx.take() {
149 Some(ctx) => ctx,
150 None => {
151 self.stopped = true;
152 return Ok(None);
153 }
154 };
155
156 let (op, node_id, op_idx, control) = extract_current_op(&resume_ctx);
158 let asmop = node_id
159 .and_then(|nid| resume_ctx.current_forest().get_assembly_op(nid, op_idx).cloned());
160
161 match poll_immediately(self.processor.step(&mut self.host, resume_ctx)) {
163 Ok(Some(new_ctx)) => {
164 self.resume_ctx = Some(new_ctx);
165 self.cycle += 1;
166
167 let state = self.processor.state();
169 let ctx = state.ctx();
170 self.current_stack = state.get_stack_state();
171
172 if self.current_context != ctx {
173 self.contexts.insert(ctx);
174 self.current_context = ctx;
175 }
176
177 self.current_op = op;
179 self.current_asmop = asmop.clone();
180
181 if let Some(op) = op {
182 if self.recent.len() == 5 {
183 self.recent.pop_front();
184 }
185 self.recent.push_back(op);
186 }
187
188 let step_info = StepInfo {
190 op,
191 control,
192 asmop: self.current_asmop.as_ref(),
193 clk: RowIndex::from(self.cycle as u32),
194 ctx: self.current_context,
195 };
196 let exited = self.callstack.next(&step_info);
197
198 Ok(exited)
199 }
200 Ok(None) => {
201 self.stopped = true;
203 let state = self.processor.state();
204 self.current_stack = state.get_stack_state();
205
206 let len = self.current_stack.len().min(16);
208 self.stack_outputs =
209 StackOutputs::new(&self.current_stack[..len]).expect("invalid stack outputs");
210 Ok(None)
211 }
212 Err(err) => {
213 self.stopped = true;
214 Err(err)
215 }
216 }
217 }
218
219 pub fn into_execution_trace(self) -> ExecutionTrace {
221 ExecutionTrace {
222 root_context: self.root_context,
223 last_cycle: RowIndex::from(self.cycle as u32),
224 processor: self.processor,
225 outputs: self.stack_outputs,
226 }
227 }
228}