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