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) 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            processor: FastProcessor::new(StackInputs::default()),
39            outputs: StackOutputs::default(),
40        }
41    }
42
43    /// Parse the program outputs on the operand stack as a value of type `T`
44    pub fn parse_result<T>(&self) -> Option<T>
45    where
46        T: FromMidenRepr,
47    {
48        let size = <T as FromMidenRepr>::size_in_felts();
49        let stack = self.outputs.get_num_elements(size);
50        if stack.len() < size {
51            return None;
52        }
53        let mut stack = stack.to_vec();
54        stack.reverse();
55        Some(<T as FromMidenRepr>::pop_from_stack(&mut stack))
56    }
57
58    /// Consume the [ExecutionTrace], extracting just the outputs on the operand stack
59    #[inline]
60    pub fn into_outputs(self) -> StackOutputs {
61        self.outputs
62    }
63
64    /// Return a reference to the operand stack outputs
65    #[inline]
66    pub fn outputs(&self) -> &StackOutputs {
67        &self.outputs
68    }
69}
70
71impl super::query::DebugQuery for ExecutionTrace {
72    fn state(&self) -> ProcessorState<'_> {
73        self.processor.state()
74    }
75
76    fn current_context(&self) -> ContextId {
77        self.processor.state().ctx()
78    }
79
80    fn current_clock(&self) -> RowIndex {
81        self.processor.state().clock()
82    }
83}
84
85impl ExecutionTrace {
86    /// Read the word at the given Miden memory address, under `ctx`, at cycle `clk`
87    pub fn read_memory_word_in_context(
88        &self,
89        addr: u32,
90        ctx: ContextId,
91        clk: RowIndex,
92    ) -> Option<Word> {
93        const ZERO: Word = Word::new([Felt::ZERO; 4]);
94
95        match self.processor.memory().read_word(
96            ctx,
97            Felt::new(addr as u64).expect("value exceeds field modulus"),
98            clk,
99        ) {
100            Ok(word) => Some(word),
101            Err(_) => Some(ZERO),
102        }
103    }
104
105    /// Read the element at the given Miden memory address, under `ctx`, at cycle `clk`
106    #[track_caller]
107    pub fn read_memory_element_in_context(
108        &self,
109        addr: u32,
110        ctx: ContextId,
111        _clk: RowIndex,
112    ) -> Option<Felt> {
113        self.processor
114            .memory()
115            .read_element(ctx, Felt::new(addr as u64).expect("value exceeds field modulus"))
116            .ok()
117    }
118
119    /// Read a raw byte vector from `addr`, under `ctx`, at cycle `clk`, sufficient to hold a value
120    /// of type `ty`
121    pub fn read_bytes_for_type_in_context(
122        &self,
123        addr: NativePtr,
124        ty: &miden_assembly_syntax::ast::types::Type,
125        ctx: ContextId,
126        clk: RowIndex,
127    ) -> Result<Vec<u8>, MemoryReadError> {
128        let size = ty.size_in_bytes();
129
130        if addr.is_element_aligned() {
131            super::query::read_memory_bytes(addr, size, |addr| {
132                Ok(self.read_memory_element_in_context(addr, ctx, clk).unwrap_or_default())
133            })
134        } else {
135            Err(MemoryReadError::UnalignedRead)
136        }
137    }
138
139    /// Read a value of the given type, given an address in Rust's address space, under `ctx`, at
140    /// cycle `clk`
141    #[track_caller]
142    pub fn read_from_rust_memory_in_context<T>(
143        &self,
144        addr: u32,
145        ctx: ContextId,
146        clk: RowIndex,
147    ) -> Option<T>
148    where
149        T: core::any::Any + FromMidenRepr,
150    {
151        let ptr = NativePtr::from_ptr(addr);
152        assert_eq!(ptr.offset, 0, "support for unaligned reads is not yet implemented");
153        let size = <T as FromMidenRepr>::size_in_felts();
154        let mut felts = SmallVec::<[_; 4]>::with_capacity(size);
155        for index in 0..(size as u32) {
156            felts.push(self.read_memory_element_in_context(ptr.addr + index, ctx, clk)?);
157        }
158        Some(T::from_felts(&felts))
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use std::sync::Arc;
165
166    use miden_assembly::DefaultSourceManager;
167    use miden_assembly_syntax::ast::types::Type;
168    use miden_processor::{ContextId, trace::RowIndex};
169
170    use super::ExecutionTrace;
171    use crate::{Executor, debug::NativePtr, exec::DebugQuery, felt::ToMidenRepr};
172
173    fn empty_trace() -> ExecutionTrace {
174        ExecutionTrace {
175            processor: miden_processor::FastProcessor::new(miden_processor::StackInputs::default()),
176            outputs: miden_processor::StackOutputs::default(),
177        }
178    }
179
180    fn execute_trace(source: &str) -> ExecutionTrace {
181        let source_manager = Arc::new(DefaultSourceManager::default());
182        let program = miden_assembly::Assembler::new(source_manager.clone())
183            .assemble_program(source)
184            .unwrap();
185
186        Executor::new(vec![]).capture_trace(&program, source_manager)
187    }
188
189    #[test]
190    fn parse_result_reads_multi_felt_outputs_in_stack_order() {
191        let outputs = 0x0807_0605_0403_0201_u64.to_felts();
192        let trace = ExecutionTrace {
193            outputs: miden_processor::StackOutputs::new(&outputs).unwrap(),
194            ..empty_trace()
195        };
196
197        let result = trace.parse_result::<u64>().unwrap();
198
199        assert_eq!(result, 0x0807_0605_0403_0201_u64);
200    }
201
202    #[test]
203    fn read_bytes_for_type_preserves_little_endian_bytes() {
204        let trace = execute_trace(
205            r#"
206begin
207    push.4660
208    push.8
209    mem_store
210
211    push.67305985
212    push.12
213    mem_store
214
215    push.134678021
216    push.13
217    mem_store
218end
219"#,
220        );
221        let ctx = ContextId::root();
222
223        let u16_bytes = trace
224            .read_bytes_for_type_in_context(
225                NativePtr::new(8, 0),
226                &Type::U16,
227                ctx,
228                RowIndex::from(0_u32),
229            )
230            .unwrap();
231        let u64_bytes = trace
232            .read_bytes_for_type_in_context(
233                NativePtr::new(12, 0),
234                &Type::U64,
235                ctx,
236                RowIndex::from(0_u32),
237            )
238            .unwrap();
239
240        assert_eq!(u16_bytes, vec![0x34, 0x12]);
241        assert_eq!(u64_bytes, vec![1, 2, 3, 4, 5, 6, 7, 8]);
242    }
243
244    #[test]
245    fn debug_query_reads_rust_memory_from_byte_address() {
246        let trace = execute_trace(
247            r#"
248begin
249    push.67305985
250    push.3
251    mem_store
252
253    push.134678021
254    push.4
255    mem_store
256end
257"#,
258        );
259
260        assert_eq!(trace.read_from_rust_memory::<u64>(12), Some(0x0807_0605_0403_0201));
261    }
262
263    #[test]
264    fn debug_query_reads_uninitialized_rust_memory_as_zero() {
265        let trace = execute_trace(
266            r#"
267begin
268    push.67305985
269    push.3
270    mem_store
271end
272"#,
273        );
274
275        assert_eq!(trace.read_from_rust_memory::<u64>(12), Some(0x0000_0000_0403_0201));
276    }
277}