Skip to main content

miden_debug_engine/exec/
trace.rs

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
8/// A callback to be executed when a [TraceEvent] occurs at a given clock cycle
9pub type TraceHandler = dyn FnMut(RowIndex, TraceEvent);
10
11/// Occurs when an attempt to read memory of the VM fails
12#[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
20/// An [ExecutionTrace] represents a final state of a program that was executed.
21///
22/// It can be used to examine the program results, and the memory of the program at
23/// any cycle up to the last cycle. It is typically used for those purposes once
24/// execution of a program terminates.
25pub 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    /// Create an empty [ExecutionTrace] with no memory and no outputs.
34    ///
35    /// Used in DAP client mode where no local execution trace is available.
36    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    /// Parse the program outputs on the operand stack as a value of type `T`
46    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    /// Consume the [ExecutionTrace], extracting just the outputs on the operand stack
61    #[inline]
62    pub fn into_outputs(self) -> StackOutputs {
63        self.outputs
64    }
65
66    /// Return a reference to the operand stack outputs
67    #[inline]
68    pub fn outputs(&self) -> &StackOutputs {
69        &self.outputs
70    }
71
72    /// Read the word at the given Miden memory address
73    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    /// Read the word at the given Miden memory address, under `ctx`, at cycle `clk`
78    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    /// Read the element at the given Miden memory address
93    #[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    /// Read the element at the given Miden memory address, under `ctx`, at cycle `clk`
102    #[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    /// Read a raw byte vector from `addr`, under `ctx`, at cycle `clk`, sufficient to hold a value
113    /// of type `ty`
114    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    /// Read a value of the given type, given an address in Rust's address space
149    #[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    /// Read a value of the given type, given an address in Rust's address space, under `ctx`, at
158    /// cycle `clk`
159    #[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}