miden_debug_engine/exec/
collect_trace.rs1use alloc::{collections::VecDeque, sync::Arc, vec::Vec};
2
3use miden_core::FMP_ADDR;
4use miden_debug_types::DefaultSourceManager;
5use miden_processor::{ExecutionError, Felt, event::EventId};
6
7use super::{
8 DebugQuery, Event, ExecutionConfig, Executor, ReplaySnapshot, source::SourceContext,
9 state::DebugExecutor,
10};
11use crate::debug::{
12 CallTrace, CallTraceRecorder, DebugVarSnapshot, FrameTransition, resolve_typed_variable_values,
13 return_felt_count, value_felt_count,
14};
15
16pub struct CallTraceReplay {
18 pub trace: CallTrace,
19 pub cycles: usize,
20 pub error: Option<ExecutionError>,
21}
22
23pub fn replay_call_trace(recording: ReplaySnapshot) -> CallTraceReplay {
28 let ReplaySnapshot {
29 package,
30 stack_inputs,
31 advice_inputs,
32 options,
33 event_log,
34 mast_forests,
35 } = recording;
36
37 let executor = Executor::from_config(ExecutionConfig {
38 inputs: stack_inputs,
39 advice_inputs,
40 options,
41 });
42 let mut executor = executor.into_debug_with_replay(
43 package,
44 Arc::new(DefaultSourceManager::default()),
45 mast_forests,
46 VecDeque::from(event_log),
47 );
48
49 let (trace, error) = collect_call_trace(&mut executor);
50
51 CallTraceReplay {
52 trace,
53 cycles: executor.cycle,
54 error,
55 }
56}
57
58fn collect_call_trace(executor: &mut DebugExecutor) -> (CallTrace, Option<ExecutionError>) {
62 let mut recorder = CallTraceRecorder::default();
63 let mut error = None;
64
65 while !executor.stopped {
66 if let Err(err) = executor.step_with_observer(|executor, source| {
67 observe_callee(&mut recorder, executor, source);
68 observe_arguments(&mut recorder, executor);
69 }) {
70 error = Some(err);
71 break;
72 }
73 if executor.stopped {
75 break;
76 }
77
78 let clk = executor.cycle;
79 let transition = executor.callstack.take_frame_transition();
81
82 let stack = match transition {
84 Some(FrameTransition::Exited) => stack_without_frame_marker(&executor.current_stack),
85 _ => Vec::new(),
86 };
87
88 match transition {
89 Some(FrameTransition::Exited) => recorder.exit(clk, &stack),
90 Some(FrameTransition::Entered) => {
92 recorder.enter(clk, executor.callstack.logical_frames(""));
93 }
94 None => {}
95 }
96 }
97
98 (recorder.finish(), error)
99}
100
101fn observe_callee(
102 recorder: &mut CallTraceRecorder,
103 executor: &DebugExecutor,
104 source: &SourceContext,
105) {
106 if !recorder.needs_callee() {
107 return;
108 }
109 if !executor.callstack.has_recovered_current_frame() || has_metadata_gap(source) {
110 recorder.stop_callee_search();
111 return;
112 }
113 let frames = executor.callstack.logical_frames("");
114 if let Some(callee) = recorder.pending_callee(&frames)
115 && let Some((info, function)) = callee.debug_info().zip(callee.function())
116 {
117 recorder.observe_callee(
118 &info[function.name_idx],
119 return_felt_count(info, function),
120 callee.invocation_id(),
121 );
122 }
123}
124
125fn has_metadata_gap(source: &SourceContext) -> bool {
135 source
136 .source_node()
137 .is_none_or(|node| node.asm_ops.is_empty() && node.inline_calls.is_empty())
138}
139
140fn observe_arguments(recorder: &mut CallTraceRecorder, executor: &DebugExecutor) {
141 if let Some(enter_clk) = recorder.accepting_args()
144 && let Some(owner) = recorder.argument_owner()
145 && executor.debug_vars.current_invocation() == Some(owner)
146 {
147 for snapshot in executor.debug_vars.current_variables() {
148 let Some(index) = snapshot.info.arg_index() else {
149 continue;
150 };
151 if usize::from(snapshot.clk) <= enter_clk {
153 continue;
154 }
155 if recorder.has_arg(index.get()) {
157 continue;
158 }
159 let ty = snapshot.info.ty();
161 let felt_count = ty.and_then(value_felt_count);
162 let values = felt_count
163 .zip(ty)
164 .and_then(|(count, ty)| resolve_arg_values(executor, snapshot, ty, count));
165 recorder.observe_arg(index.get(), snapshot.info.name(), felt_count, values);
166 }
167 }
168}
169
170fn resolve_arg_values(
172 executor: &DebugExecutor,
173 snapshot: &DebugVarSnapshot,
174 ty: &miden_assembly_syntax::ast::types::Type,
175 count: usize,
176) -> Option<Vec<u64>> {
177 let to_u64 = |values: Vec<Felt>| values.iter().map(Felt::as_canonical_u64).collect();
178
179 if let Some(captured) = executor.debug_vars.captured_values(snapshot.info.name())
183 && captured.len() == count
184 {
185 return Some(to_u64(captured.to_vec()));
186 }
187
188 let read_memory = |addr: u32| executor.read_memory_element(addr);
189 let read_local = |offset: i16| {
190 let fmp = read_memory(FMP_ADDR.as_canonical_u64() as u32)?;
191 let fmp = u32::try_from(fmp.as_canonical_u64()).ok()?;
192 read_memory(fmp.checked_add_signed(i32::from(offset))?)
193 };
194
195 resolve_typed_variable_values(
196 snapshot.info.value_location(),
197 ty,
198 count,
199 &executor.current_stack,
200 read_memory,
201 read_local,
202 )
203 .map(to_u64)
204}
205
206fn stack_without_frame_marker(stack: &[Felt]) -> Vec<u64> {
207 let is_marker = |value: &Felt| {
209 let id = EventId::from_felt(*value);
210 id == Event::FrameStart.as_event_id() || id == Event::FrameEnd.as_event_id()
211 };
212
213 let rest = match stack.split_first() {
214 Some((top, rest)) if is_marker(top) => rest,
215 _ => stack,
216 };
217
218 rest.iter().map(Felt::as_canonical_u64).collect()
219}
220
221#[cfg(test)]
222mod tests;