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