miden_debug_engine/exec/
trace.rs1use miden_core::Word;
2use miden_processor::{
3 ContextId, FastProcessor, Felt, ProcessorState, StackInputs, StackOutputs, trace::RowIndex,
4};
5use smallvec::SmallVec;
6
7use super::TraceEvent;
8use crate::{debug::NativePtr, felt::FromMidenRepr};
9
10pub type TraceHandler = dyn FnMut(&ProcessorState<'_>, TraceEvent);
12
13#[derive(Debug, thiserror::Error)]
15pub enum MemoryReadError {
16 #[error("attempted to read beyond end of linear memory")]
17 OutOfBounds,
18 #[error("unaligned reads are not supported yet")]
19 UnalignedRead,
20}
21
22pub struct ExecutionTrace {
28 pub(super) processor: FastProcessor,
29 pub(super) outputs: StackOutputs,
30}
31
32impl ExecutionTrace {
33 pub fn empty() -> Self {
37 Self {
38 processor: FastProcessor::new(StackInputs::default()),
39 outputs: StackOutputs::default(),
40 }
41 }
42
43 pub fn parse_result<T>(&self) -> Option<T>
45 where
46 T: FromMidenRepr,
47 {
48 let size = <T as FromMidenRepr>::size_in_felts();
49 let stack = self.outputs.get_num_elements(size);
50 if stack.len() < size {
51 return None;
52 }
53 let mut stack = stack.to_vec();
54 stack.reverse();
55 Some(<T as FromMidenRepr>::pop_from_stack(&mut stack))
56 }
57
58 #[inline]
60 pub fn into_outputs(self) -> StackOutputs {
61 self.outputs
62 }
63
64 #[inline]
66 pub fn outputs(&self) -> &StackOutputs {
67 &self.outputs
68 }
69}
70
71impl super::query::DebugQuery for ExecutionTrace {
72 fn state(&self) -> ProcessorState<'_> {
73 self.processor.state()
74 }
75
76 fn current_context(&self) -> ContextId {
77 self.processor.state().ctx()
78 }
79
80 fn current_clock(&self) -> RowIndex {
81 self.processor.state().clock()
82 }
83}
84
85impl ExecutionTrace {
86 pub fn read_memory_word_in_context(
88 &self,
89 addr: u32,
90 ctx: ContextId,
91 clk: RowIndex,
92 ) -> Option<Word> {
93 const ZERO: Word = Word::new([Felt::ZERO; 4]);
94
95 match self.processor.memory().read_word(
96 ctx,
97 Felt::new(addr as u64).expect("value exceeds field modulus"),
98 clk,
99 ) {
100 Ok(word) => Some(word),
101 Err(_) => Some(ZERO),
102 }
103 }
104
105 #[track_caller]
107 pub fn read_memory_element_in_context(
108 &self,
109 addr: u32,
110 ctx: ContextId,
111 _clk: RowIndex,
112 ) -> Option<Felt> {
113 self.processor
114 .memory()
115 .read_element(ctx, Felt::new(addr as u64).expect("value exceeds field modulus"))
116 .ok()
117 }
118
119 pub fn read_bytes_for_type_in_context(
122 &self,
123 addr: NativePtr,
124 ty: &miden_assembly_syntax::ast::types::Type,
125 ctx: ContextId,
126 clk: RowIndex,
127 ) -> Result<Vec<u8>, MemoryReadError> {
128 let size = ty.size_in_bytes();
129
130 if addr.is_element_aligned() {
131 super::query::read_memory_bytes(addr, size, |addr| {
132 Ok(self.read_memory_element_in_context(addr, ctx, clk).unwrap_or_default())
133 })
134 } else {
135 Err(MemoryReadError::UnalignedRead)
136 }
137 }
138
139 #[track_caller]
142 pub fn read_from_rust_memory_in_context<T>(
143 &self,
144 addr: u32,
145 ctx: ContextId,
146 clk: RowIndex,
147 ) -> Option<T>
148 where
149 T: core::any::Any + FromMidenRepr,
150 {
151 let ptr = NativePtr::from_ptr(addr);
152 assert_eq!(ptr.offset, 0, "support for unaligned reads is not yet implemented");
153 let size = <T as FromMidenRepr>::size_in_felts();
154 let mut felts = SmallVec::<[_; 4]>::with_capacity(size);
155 for index in 0..(size as u32) {
156 felts.push(self.read_memory_element_in_context(ptr.addr + index, ctx, clk)?);
157 }
158 Some(T::from_felts(&felts))
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use std::sync::Arc;
165
166 use miden_assembly::DefaultSourceManager;
167 use miden_assembly_syntax::ast::types::Type;
168 use miden_processor::{ContextId, trace::RowIndex};
169
170 use super::ExecutionTrace;
171 use crate::{Executor, debug::NativePtr, exec::DebugQuery, felt::ToMidenRepr};
172
173 fn empty_trace() -> ExecutionTrace {
174 ExecutionTrace {
175 processor: miden_processor::FastProcessor::new(miden_processor::StackInputs::default()),
176 outputs: miden_processor::StackOutputs::default(),
177 }
178 }
179
180 fn execute_trace(source: &str) -> ExecutionTrace {
181 let source_manager = Arc::new(DefaultSourceManager::default());
182 let program = miden_assembly::Assembler::new(source_manager.clone())
183 .assemble_program(source)
184 .unwrap();
185
186 Executor::new(vec![]).capture_trace(&program, source_manager)
187 }
188
189 #[test]
190 fn parse_result_reads_multi_felt_outputs_in_stack_order() {
191 let outputs = 0x0807_0605_0403_0201_u64.to_felts();
192 let trace = ExecutionTrace {
193 outputs: miden_processor::StackOutputs::new(&outputs).unwrap(),
194 ..empty_trace()
195 };
196
197 let result = trace.parse_result::<u64>().unwrap();
198
199 assert_eq!(result, 0x0807_0605_0403_0201_u64);
200 }
201
202 #[test]
203 fn read_bytes_for_type_preserves_little_endian_bytes() {
204 let trace = execute_trace(
205 r#"
206begin
207 push.4660
208 push.8
209 mem_store
210
211 push.67305985
212 push.12
213 mem_store
214
215 push.134678021
216 push.13
217 mem_store
218end
219"#,
220 );
221 let ctx = ContextId::root();
222
223 let u16_bytes = trace
224 .read_bytes_for_type_in_context(
225 NativePtr::new(8, 0),
226 &Type::U16,
227 ctx,
228 RowIndex::from(0_u32),
229 )
230 .unwrap();
231 let u64_bytes = trace
232 .read_bytes_for_type_in_context(
233 NativePtr::new(12, 0),
234 &Type::U64,
235 ctx,
236 RowIndex::from(0_u32),
237 )
238 .unwrap();
239
240 assert_eq!(u16_bytes, vec![0x34, 0x12]);
241 assert_eq!(u64_bytes, vec![1, 2, 3, 4, 5, 6, 7, 8]);
242 }
243
244 #[test]
245 fn debug_query_reads_rust_memory_from_byte_address() {
246 let trace = execute_trace(
247 r#"
248begin
249 push.67305985
250 push.3
251 mem_store
252
253 push.134678021
254 push.4
255 mem_store
256end
257"#,
258 );
259
260 assert_eq!(trace.read_from_rust_memory::<u64>(12), Some(0x0807_0605_0403_0201));
261 }
262
263 #[test]
264 fn debug_query_reads_uninitialized_rust_memory_as_zero() {
265 let trace = execute_trace(
266 r#"
267begin
268 push.67305985
269 push.3
270 mem_store
271end
272"#,
273 );
274
275 assert_eq!(trace.read_from_rust_memory::<u64>(12), Some(0x0000_0000_0403_0201));
276 }
277}