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