Skip to main content

miden_debug_engine/exec/
trace.rs

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