miden_debug/exec/
trace.rs1use miden_core::Word;
2use miden_processor::{ContextId, FastProcessor, Felt, StackOutputs, trace::RowIndex};
3use smallvec::SmallVec;
4
5use super::TraceEvent;
6use crate::{debug::NativePtr, felt::FromMidenRepr};
7
8pub type TraceHandler = dyn FnMut(RowIndex, TraceEvent);
10
11#[derive(Debug, thiserror::Error)]
13pub enum MemoryReadError {
14 #[error("attempted to read beyond end of linear memory")]
15 OutOfBounds,
16 #[error("unaligned reads are not supported yet")]
17 UnalignedRead,
18}
19
20pub struct ExecutionTrace {
26 pub(super) root_context: ContextId,
27 pub(super) last_cycle: RowIndex,
28 pub(super) processor: FastProcessor,
29 pub(super) outputs: StackOutputs,
30}
31
32impl ExecutionTrace {
33 pub fn parse_result<T>(&self) -> Option<T>
35 where
36 T: FromMidenRepr,
37 {
38 let size = <T as FromMidenRepr>::size_in_felts();
39 let stack = self.outputs.get_num_elements(size);
40 if stack.len() < size {
41 return None;
42 }
43 let mut stack = stack.to_vec();
44 stack.reverse();
45 Some(<T as FromMidenRepr>::pop_from_stack(&mut stack))
46 }
47
48 #[inline]
50 pub fn into_outputs(self) -> StackOutputs {
51 self.outputs
52 }
53
54 #[inline]
56 pub fn outputs(&self) -> &StackOutputs {
57 &self.outputs
58 }
59
60 pub fn read_memory_word(&self, addr: u32) -> Option<Word> {
62 self.read_memory_word_in_context(addr, self.root_context, self.last_cycle)
63 }
64
65 pub fn read_memory_word_in_context(
67 &self,
68 addr: u32,
69 ctx: ContextId,
70 clk: RowIndex,
71 ) -> Option<Word> {
72 const ZERO: Word = Word::new([Felt::ZERO; 4]);
73
74 match self.processor.memory().read_word(ctx, Felt::new(addr as u64), clk) {
75 Ok(word) => Some(word),
76 Err(_) => Some(ZERO),
77 }
78 }
79
80 #[track_caller]
82 pub fn read_memory_element(&self, addr: u32) -> Option<Felt> {
83 self.processor
84 .memory()
85 .read_element(self.root_context, Felt::new(addr as u64))
86 .ok()
87 }
88
89 #[track_caller]
91 pub fn read_memory_element_in_context(
92 &self,
93 addr: u32,
94 ctx: ContextId,
95 _clk: RowIndex,
96 ) -> Option<Felt> {
97 self.processor.memory().read_element(ctx, Felt::new(addr as u64)).ok()
98 }
99
100 pub fn read_bytes_for_type(
103 &self,
104 addr: NativePtr,
105 ty: &miden_assembly_syntax::ast::types::Type,
106 ctx: ContextId,
107 clk: RowIndex,
108 ) -> Result<Vec<u8>, MemoryReadError> {
109 const U32_MASK: u64 = u32::MAX as u64;
110 let size = ty.size_in_bytes();
111 let mut buf = Vec::with_capacity(size);
112
113 let size_in_felts = ty.size_in_felts();
114 let mut elems = Vec::with_capacity(size_in_felts);
115
116 if addr.is_element_aligned() {
117 for i in 0..size_in_felts {
118 let addr = addr.addr.checked_add(i as u32).ok_or(MemoryReadError::OutOfBounds)?;
119 elems.push(self.read_memory_element_in_context(addr, ctx, clk).unwrap_or_default());
120 }
121 } else {
122 return Err(MemoryReadError::UnalignedRead);
123 }
124
125 let mut needed = size - buf.len();
126 for elem in elems {
127 let bytes = ((elem.as_canonical_u64() & U32_MASK) as u32).to_le_bytes();
128 let take = core::cmp::min(needed, 4);
129 buf.extend(&bytes[0..take]);
130 needed -= take;
131 }
132
133 Ok(buf)
134 }
135
136 #[track_caller]
138 pub fn read_from_rust_memory<T>(&self, addr: u32) -> Option<T>
139 where
140 T: core::any::Any + FromMidenRepr,
141 {
142 self.read_from_rust_memory_in_context(addr, self.root_context, self.last_cycle)
143 }
144
145 #[track_caller]
148 pub fn read_from_rust_memory_in_context<T>(
149 &self,
150 addr: u32,
151 ctx: ContextId,
152 clk: RowIndex,
153 ) -> Option<T>
154 where
155 T: core::any::Any + FromMidenRepr,
156 {
157 let ptr = NativePtr::from_ptr(addr);
158 assert_eq!(ptr.offset, 0, "support for unaligned reads is not yet implemented");
159 let size = <T as FromMidenRepr>::size_in_felts();
160 let mut felts = SmallVec::<[_; 4]>::with_capacity(size);
161 for index in 0..(size as u32) {
162 felts.push(self.read_memory_element_in_context(ptr.addr + index, ctx, clk)?);
163 }
164 Some(T::from_felts(&felts))
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use std::sync::Arc;
171
172 use miden_assembly::DefaultSourceManager;
173 use miden_assembly_syntax::ast::types::Type;
174 use miden_processor::{ContextId, trace::RowIndex};
175
176 use super::ExecutionTrace;
177 use crate::{Executor, debug::NativePtr, felt::ToMidenRepr};
178
179 fn empty_trace() -> ExecutionTrace {
180 ExecutionTrace {
181 root_context: ContextId::root(),
182 last_cycle: RowIndex::from(0_u32),
183 processor: miden_processor::FastProcessor::new(miden_processor::StackInputs::default()),
184 outputs: miden_processor::StackOutputs::default(),
185 }
186 }
187
188 fn execute_trace(source: &str) -> ExecutionTrace {
189 let source_manager = Arc::new(DefaultSourceManager::default());
190 let program = miden_assembly::Assembler::new(source_manager.clone())
191 .assemble_program(source)
192 .unwrap();
193
194 Executor::new(vec![]).capture_trace(&program, source_manager)
195 }
196
197 #[test]
198 fn parse_result_reads_multi_felt_outputs_in_stack_order() {
199 let outputs = 0x0807_0605_0403_0201_u64.to_felts();
200 let trace = ExecutionTrace {
201 outputs: miden_processor::StackOutputs::new(&outputs).unwrap(),
202 ..empty_trace()
203 };
204
205 let result = trace.parse_result::<u64>().unwrap();
206
207 assert_eq!(result, 0x0807_0605_0403_0201_u64);
208 }
209
210 #[test]
211 fn read_bytes_for_type_preserves_little_endian_bytes() {
212 let trace = execute_trace(
213 r#"
214begin
215 push.4660
216 push.8
217 mem_store
218
219 push.67305985
220 push.12
221 mem_store
222
223 push.134678021
224 push.13
225 mem_store
226end
227"#,
228 );
229 let ctx = ContextId::root();
230
231 let u16_bytes = trace
232 .read_bytes_for_type(NativePtr::new(8, 0), &Type::U16, ctx, RowIndex::from(0_u32))
233 .unwrap();
234 let u64_bytes = trace
235 .read_bytes_for_type(NativePtr::new(12, 0), &Type::U64, ctx, RowIndex::from(0_u32))
236 .unwrap();
237
238 assert_eq!(u16_bytes, vec![0x34, 0x12]);
239 assert_eq!(u64_bytes, vec![1, 2, 3, 4, 5, 6, 7, 8]);
240 }
241}