Skip to main content

miden_debug/exec/
trace.rs

1use miden_core::Word;
2use miden_processor::{ContextId, FastProcessor, Felt, 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    /// Parse the program outputs on the operand stack as a value of type `T`
34    pub fn parse_result<T>(&self) -> Option<T>
35    where
36        T: FromMidenRepr,
37    {
38        let size = <T as FromMidenRepr>::size_in_felts();
39        let stack = self.outputs.get_num_elements(size);
40        if stack.len() < size {
41            return None;
42        }
43        let mut stack = stack.to_vec();
44        stack.reverse();
45        Some(<T as FromMidenRepr>::pop_from_stack(&mut stack))
46    }
47
48    /// Consume the [ExecutionTrace], extracting just the outputs on the operand stack
49    #[inline]
50    pub fn into_outputs(self) -> StackOutputs {
51        self.outputs
52    }
53
54    /// Return a reference to the operand stack outputs
55    #[inline]
56    pub fn outputs(&self) -> &StackOutputs {
57        &self.outputs
58    }
59
60    /// Read the word at the given Miden memory address
61    pub fn read_memory_word(&self, addr: u32) -> Option<Word> {
62        self.read_memory_word_in_context(addr, self.root_context, self.last_cycle)
63    }
64
65    /// Read the word at the given Miden memory address, under `ctx`, at cycle `clk`
66    pub fn read_memory_word_in_context(
67        &self,
68        addr: u32,
69        ctx: ContextId,
70        clk: RowIndex,
71    ) -> Option<Word> {
72        const ZERO: Word = Word::new([Felt::ZERO; 4]);
73
74        match self.processor.memory().read_word(ctx, Felt::new(addr as u64), clk) {
75            Ok(word) => Some(word),
76            Err(_) => Some(ZERO),
77        }
78    }
79
80    /// Read the element at the given Miden memory address
81    #[track_caller]
82    pub fn read_memory_element(&self, addr: u32) -> Option<Felt> {
83        self.processor
84            .memory()
85            .read_element(self.root_context, Felt::new(addr as u64))
86            .ok()
87    }
88
89    /// Read the element at the given Miden memory address, under `ctx`, at cycle `clk`
90    #[track_caller]
91    pub fn read_memory_element_in_context(
92        &self,
93        addr: u32,
94        ctx: ContextId,
95        _clk: RowIndex,
96    ) -> Option<Felt> {
97        self.processor.memory().read_element(ctx, Felt::new(addr as u64)).ok()
98    }
99
100    /// Read a raw byte vector from `addr`, under `ctx`, at cycle `clk`, sufficient to hold a value
101    /// of type `ty`
102    pub fn read_bytes_for_type(
103        &self,
104        addr: NativePtr,
105        ty: &miden_assembly_syntax::ast::types::Type,
106        ctx: ContextId,
107        clk: RowIndex,
108    ) -> Result<Vec<u8>, MemoryReadError> {
109        const U32_MASK: u64 = u32::MAX as u64;
110        let size = ty.size_in_bytes();
111        let mut buf = Vec::with_capacity(size);
112
113        let size_in_felts = ty.size_in_felts();
114        let mut elems = Vec::with_capacity(size_in_felts);
115
116        if addr.is_element_aligned() {
117            for i in 0..size_in_felts {
118                let addr = addr.addr.checked_add(i as u32).ok_or(MemoryReadError::OutOfBounds)?;
119                elems.push(self.read_memory_element_in_context(addr, ctx, clk).unwrap_or_default());
120            }
121        } else {
122            return Err(MemoryReadError::UnalignedRead);
123        }
124
125        let mut needed = size - buf.len();
126        for elem in elems {
127            let bytes = ((elem.as_canonical_u64() & U32_MASK) as u32).to_be_bytes();
128            let take = core::cmp::min(needed, 4);
129            buf.extend(&bytes[0..take]);
130            needed -= take;
131        }
132
133        Ok(buf)
134    }
135
136    /// Read a value of the given type, given an address in Rust's address space
137    #[track_caller]
138    pub fn read_from_rust_memory<T>(&self, addr: u32) -> Option<T>
139    where
140        T: core::any::Any + FromMidenRepr,
141    {
142        self.read_from_rust_memory_in_context(addr, self.root_context, self.last_cycle)
143    }
144
145    /// Read a value of the given type, given an address in Rust's address space, under `ctx`, at
146    /// cycle `clk`
147    #[track_caller]
148    pub fn read_from_rust_memory_in_context<T>(
149        &self,
150        addr: u32,
151        ctx: ContextId,
152        clk: RowIndex,
153    ) -> Option<T>
154    where
155        T: core::any::Any + FromMidenRepr,
156    {
157        use core::any::TypeId;
158
159        let ptr = NativePtr::from_ptr(addr);
160        if TypeId::of::<T>() == TypeId::of::<Felt>() {
161            assert_eq!(ptr.offset, 0, "cannot read values of type Felt from unaligned addresses");
162        }
163        assert_eq!(ptr.offset, 0, "support for unaligned reads is not yet implemented");
164        match <T as FromMidenRepr>::size_in_felts() {
165            1 => {
166                let felt = self.read_memory_element_in_context(ptr.addr, ctx, clk)?;
167                Some(T::from_felts(&[felt]))
168            }
169            2 => {
170                let lo = self.read_memory_element_in_context(ptr.addr, ctx, clk)?;
171                let hi = self.read_memory_element_in_context(ptr.addr + 1, ctx, clk)?;
172                Some(T::from_felts(&[lo, hi]))
173            }
174            3 => {
175                let lo_l = self.read_memory_element_in_context(ptr.addr, ctx, clk)?;
176                let lo_h = self.read_memory_element_in_context(ptr.addr + 1, ctx, clk)?;
177                let hi_l = self.read_memory_element_in_context(ptr.addr + 2, ctx, clk)?;
178                Some(T::from_felts(&[lo_l, lo_h, hi_l]))
179            }
180            n => {
181                assert_ne!(n, 0);
182                let num_words = n.next_multiple_of(4) / 4;
183                let mut words = SmallVec::<[_; 2]>::with_capacity(num_words);
184                for word_index in 0..(num_words as u32) {
185                    let addr = ptr.addr + (word_index * 4);
186                    let mut word = self.read_memory_word(addr)?;
187                    word.reverse();
188                    dbg!(word_index, word);
189                    words.push(word);
190                }
191                words.resize(num_words, Word::new([Felt::ZERO; 4]));
192                Some(T::from_words(&words))
193            }
194        }
195    }
196}