miden_debug/exec/
trace.rs

1use miden_assembly_syntax::ast::types::Type;
2use miden_core::{FieldElement, Word};
3use miden_processor::{ContextId, Felt, RowIndex, StackOutputs, TraceLenSummary};
4use smallvec::SmallVec;
5
6use super::{MemoryChiplet, TraceEvent};
7use crate::{debug::NativePtr, felt::FromMidenRepr};
8
9/// A callback to be executed when a [TraceEvent] occurs at a given clock cycle
10pub type TraceHandler = dyn FnMut(RowIndex, TraceEvent);
11
12/// Occurs when an attempt to read memory of the VM fails
13#[derive(Debug, thiserror::Error)]
14pub enum MemoryReadError {
15    #[error("attempted to read beyond end of linear memory")]
16    OutOfBounds,
17    #[error("unaligned reads are not supported yet")]
18    UnalignedRead,
19}
20
21/// An [ExecutionTrace] represents a final state of a program that was executed.
22///
23/// It can be used to examine the program results, and the memory of the program at
24/// any cycle up to the last cycle. It is typically used for those purposes once
25/// execution of a program terminates.
26pub struct ExecutionTrace {
27    pub(super) root_context: ContextId,
28    pub(super) last_cycle: RowIndex,
29    pub(super) memory: MemoryChiplet,
30    pub(super) outputs: StackOutputs,
31    pub(super) trace_len_summary: TraceLenSummary,
32}
33
34impl ExecutionTrace {
35    /// Parse the program outputs on the operand stack as a value of type `T`
36    pub fn parse_result<T>(&self) -> Option<T>
37    where
38        T: FromMidenRepr,
39    {
40        let size = <T as FromMidenRepr>::size_in_felts();
41        let stack = self.outputs.stack_truncated(size);
42        if stack.len() < size {
43            return None;
44        }
45        let mut stack = stack.to_vec();
46        stack.reverse();
47        Some(<T as FromMidenRepr>::pop_from_stack(&mut stack))
48    }
49
50    /// Consume the [ExecutionTrace], extracting just the outputs on the operand stack
51    #[inline]
52    pub fn into_outputs(self) -> StackOutputs {
53        self.outputs
54    }
55
56    /// Return a reference to the operand stack outputs
57    #[inline]
58    pub fn outputs(&self) -> &StackOutputs {
59        &self.outputs
60    }
61
62    /// Return a reference to the trace length summary
63    #[inline]
64    pub fn trace_len_summary(&self) -> &TraceLenSummary {
65        &self.trace_len_summary
66    }
67
68    /// Read the word at the given Miden memory address
69    pub fn read_memory_word(&self, addr: u32) -> Option<Word> {
70        self.read_memory_word_in_context(addr, self.root_context, self.last_cycle)
71    }
72
73    /// Read the word at the given Miden memory address, under `ctx`, at cycle `clk`
74    pub fn read_memory_word_in_context(
75        &self,
76        addr: u32,
77        ctx: ContextId,
78        _clk: RowIndex,
79    ) -> Option<Word> {
80        use miden_core::FieldElement;
81
82        const ZERO: Word = Word::new([Felt::ZERO; 4]);
83
84        Some(
85            self.memory
86                .get_word(ctx, addr)
87                .unwrap_or_else(|err| panic!("{err}"))
88                .unwrap_or(ZERO),
89        )
90    }
91
92    /// Read the word at the given Miden memory address and element offset
93    #[track_caller]
94    pub fn read_memory_element(&self, addr: u32) -> Option<Felt> {
95        self.memory.get_value(self.root_context, addr)
96    }
97
98    /// Read the word at the given Miden memory address and element offset, under `ctx`, at cycle
99    /// `clk`
100    #[track_caller]
101    pub fn read_memory_element_in_context(
102        &self,
103        addr: u32,
104        ctx: ContextId,
105        _clk: RowIndex,
106    ) -> Option<Felt> {
107        self.memory.get_value(ctx, addr)
108    }
109
110    /// Read a raw byte vector from `addr`, under `ctx`, at cycle `clk`, sufficient to hold a value
111    /// of type `ty`
112    pub fn read_bytes_for_type(
113        &self,
114        addr: NativePtr,
115        ty: &Type,
116        ctx: ContextId,
117        clk: RowIndex,
118    ) -> Result<Vec<u8>, MemoryReadError> {
119        const U32_MASK: u64 = u32::MAX as u64;
120        let size = ty.size_in_bytes();
121        let mut buf = Vec::with_capacity(size);
122
123        let size_in_felts = ty.size_in_felts();
124        let mut elems = Vec::with_capacity(size_in_felts);
125
126        if addr.is_element_aligned() {
127            for i in 0..size_in_felts {
128                let addr = addr.addr.checked_add(i as u32).ok_or(MemoryReadError::OutOfBounds)?;
129                elems.push(self.read_memory_element_in_context(addr, ctx, clk).unwrap_or_default());
130            }
131        } else {
132            return Err(MemoryReadError::UnalignedRead);
133        }
134
135        let mut needed = size - buf.len();
136        for elem in elems {
137            let bytes = ((elem.as_int() & U32_MASK) as u32).to_be_bytes();
138            let take = core::cmp::min(needed, 4);
139            buf.extend(&bytes[0..take]);
140            needed -= take;
141        }
142
143        Ok(buf)
144    }
145
146    /// Read a value of the given type, given an address in Rust's address space
147    #[track_caller]
148    pub fn read_from_rust_memory<T>(&self, addr: u32) -> Option<T>
149    where
150        T: core::any::Any + FromMidenRepr,
151    {
152        self.read_from_rust_memory_in_context(addr, self.root_context, self.last_cycle)
153    }
154
155    /// Read a value of the given type, given an address in Rust's address space, under `ctx`, at
156    /// cycle `clk`
157    #[track_caller]
158    pub fn read_from_rust_memory_in_context<T>(
159        &self,
160        addr: u32,
161        ctx: ContextId,
162        clk: RowIndex,
163    ) -> Option<T>
164    where
165        T: core::any::Any + FromMidenRepr,
166    {
167        use core::any::TypeId;
168
169        let ptr = NativePtr::from_ptr(addr);
170        if TypeId::of::<T>() == TypeId::of::<Felt>() {
171            assert_eq!(ptr.offset, 0, "cannot read values of type Felt from unaligned addresses");
172        }
173        assert_eq!(ptr.offset, 0, "support for unaligned reads is not yet implemented");
174        match <T as FromMidenRepr>::size_in_felts() {
175            1 => {
176                let felt = self.read_memory_element_in_context(ptr.addr, ctx, clk)?;
177                Some(T::from_felts(&[felt]))
178            }
179            2 => {
180                let lo = self.read_memory_element_in_context(ptr.addr, ctx, clk)?;
181                let hi = self.read_memory_element_in_context(ptr.addr + 1, ctx, clk)?;
182                Some(T::from_felts(&[lo, hi]))
183            }
184            3 => {
185                let lo_l = self.read_memory_element_in_context(ptr.addr, ctx, clk)?;
186                let lo_h = self.read_memory_element_in_context(ptr.addr + 1, ctx, clk)?;
187                let hi_l = self.read_memory_element_in_context(ptr.addr + 2, ctx, clk)?;
188                Some(T::from_felts(&[lo_l, lo_h, hi_l]))
189            }
190            n => {
191                assert_ne!(n, 0);
192                let num_words = n.next_multiple_of(4) / 4;
193                let mut words = SmallVec::<[_; 2]>::with_capacity(num_words);
194                for word_index in 0..(num_words as u32) {
195                    let addr = ptr.addr + (word_index * 4);
196                    let mut word = self.read_memory_word(addr)?;
197                    word.reverse();
198                    dbg!(word_index, word);
199                    words.push(word);
200                }
201                words.resize(num_words, Word::new([Felt::ZERO; 4]));
202                Some(T::from_words(&words))
203            }
204        }
205    }
206}