Skip to main content

miden_debug_engine/exec/
trace.rs

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
10/// A callback to be executed when a [TraceEvent] occurs
11pub type TraceHandler = dyn FnMut(&ProcessorState<'_>, TraceEvent);
12
13/// Occurs when an attempt to read memory of the VM fails
14#[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
22/// An [ExecutionTrace] represents a final state of a program that was executed.
23///
24/// It can be used to examine the program results, and the memory of the program at
25/// any cycle up to the last cycle. It is typically used for those purposes once
26/// execution of a program terminates.
27pub 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    /// Create an empty [ExecutionTrace] with no memory and no outputs.
36    ///
37    /// Used in DAP client mode where no local execution trace is available.
38    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    /// Parse the program outputs on the operand stack as a value of type `T`
48    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    /// Consume the [ExecutionTrace], extracting just the outputs on the operand stack
63    #[inline]
64    pub fn into_outputs(self) -> StackOutputs {
65        self.outputs
66    }
67
68    /// Return a reference to the operand stack outputs
69    #[inline]
70    pub fn outputs(&self) -> &StackOutputs {
71        &self.outputs
72    }
73
74    /// Read the word at the given Miden memory address
75    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    /// Read the word at the given Miden memory address, under `ctx`, at cycle `clk`
80    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    /// Read the element at the given Miden memory address
95    #[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    /// Read the element at the given Miden memory address, under `ctx`, at cycle `clk`
104    #[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    /// Read a raw byte vector from `addr`, under `ctx`, at cycle `clk`, sufficient to hold a value
115    /// of type `ty`
116    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    /// Read a value of the given type, given an address in Rust's address space
135    #[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    /// Read a value of the given type, given an address in Rust's address space, under `ctx`, at
144    /// cycle `clk`
145    #[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
170/// Reads `size` bytes from memory, starting at `ptr`. Handles `ptr`'s offset.
171///
172/// The `read_elem` callback is used to fetch an element from an element address.
173pub(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}