Skip to main content

miden_debug_engine/debug/
variables.rs

1use std::{cell::RefCell, collections::BTreeMap, rc::Rc};
2
3use miden_core::{
4    Felt,
5    operations::{DebugVarInfo, DebugVarLocation},
6};
7use miden_processor::trace::RowIndex;
8
9const FRAME_BASE_LOCAL_MARKER: u32 = 1 << 31;
10
11fn decode_frame_base_local_offset(encoded: u32) -> Option<i16> {
12    if encoded & FRAME_BASE_LOCAL_MARKER == 0 {
13        return None;
14    }
15
16    let low_bits = (encoded & 0xffff) as u16;
17    Some(i16::from_le_bytes(low_bits.to_le_bytes()))
18}
19
20/// A snapshot of a debug variable at a specific clock cycle.
21#[derive(Debug, Clone)]
22pub struct DebugVarSnapshot {
23    /// The clock cycle when this variable info was recorded.
24    pub clk: RowIndex,
25    /// The debug variable information.
26    pub info: DebugVarInfo,
27}
28
29/// Tracks debug variable snapshots, mapping variable names to their most recent location info.
30pub struct DebugVarTracker {
31    /// All debug variable events recorded during execution, keyed by clock cycle.
32    events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>>,
33    /// Current view of variables - maps variable name to most recent info.
34    current_vars: BTreeMap<String, DebugVarSnapshot>,
35    /// The clock cycle up to which we've processed events.
36    processed_up_to: RowIndex,
37}
38
39impl DebugVarTracker {
40    /// Create a new tracker using the given shared event store.
41    pub fn new(events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>>) -> Self {
42        Self {
43            events,
44            current_vars: BTreeMap::new(),
45            processed_up_to: RowIndex::from(0),
46        }
47    }
48
49    /// Record debug variable events at the given clock cycle.
50    pub fn record_events(&self, clk: RowIndex, infos: Vec<DebugVarInfo>) {
51        if !infos.is_empty() {
52            self.events.borrow_mut().entry(clk).or_default().extend(infos);
53        }
54    }
55
56    /// Process all events up to and including `clk`, updating current variable state.
57    pub fn update_to_cycle(&mut self, clk: RowIndex) {
58        let events = self.events.borrow();
59
60        // Process events from processed_up_to to clk
61        for (event_clk, var_infos) in events.range(self.processed_up_to..=clk) {
62            for info in var_infos {
63                let snapshot = DebugVarSnapshot {
64                    clk: *event_clk,
65                    info: info.clone(),
66                };
67                self.current_vars.insert(info.name().to_string(), snapshot);
68            }
69        }
70
71        self.processed_up_to = clk;
72    }
73
74    /// Reset the tracker to the beginning of execution.
75    pub fn reset(&mut self) {
76        self.current_vars.clear();
77        self.processed_up_to = RowIndex::from(0);
78    }
79
80    /// Get all currently visible variables.
81    pub fn current_variables(&self) -> impl Iterator<Item = &DebugVarSnapshot> {
82        self.current_vars.values()
83    }
84
85    /// Get a specific variable by name.
86    pub fn get_variable(&self, name: &str) -> Option<&DebugVarSnapshot> {
87        self.current_vars.get(name)
88    }
89
90    /// Get the number of tracked variables.
91    pub fn variable_count(&self) -> usize {
92        self.current_vars.len()
93    }
94
95    /// Check if there are any tracked variables.
96    pub fn has_variables(&self) -> bool {
97        !self.current_vars.is_empty()
98    }
99}
100
101/// Resolve a debug variable's value given its location and the current VM state.
102pub fn resolve_variable_value(
103    location: &DebugVarLocation,
104    stack: &[Felt],
105    get_memory: impl Fn(u32) -> Option<Felt>,
106    get_local: impl Fn(i16) -> Option<Felt>,
107) -> Option<Felt> {
108    match location {
109        DebugVarLocation::Stack(pos) => stack.get(*pos as usize).copied(),
110        DebugVarLocation::Memory(addr) => get_memory(*addr),
111        DebugVarLocation::Const(felt) => Some(*felt),
112        DebugVarLocation::Local(offset) => get_local(*offset),
113        DebugVarLocation::FrameBase {
114            global_index,
115            byte_offset,
116        } => {
117            if let Some(local_offset) = decode_frame_base_local_offset(*global_index) {
118                let base = get_local(local_offset)?;
119                let byte_addr = base.as_canonical_u64() as i64 + byte_offset;
120                let elem_addr = u32::try_from(byte_addr / 4).ok()?;
121                return get_memory(elem_addr);
122            }
123
124            // global_index was resolved to a Miden byte address during compilation.
125            // Convert to element address (รท4) to read the stack pointer value.
126            let sp_elem_addr = *global_index / 4;
127            let base = get_memory(sp_elem_addr)?;
128            // The stack pointer value is also a byte address; apply byte_offset,
129            // then convert to element address to read the variable's value.
130            let byte_addr = base.as_canonical_u64() as i64 + byte_offset;
131            let elem_addr = (byte_addr / 4) as u32;
132            get_memory(elem_addr)
133        }
134        DebugVarLocation::Expression(_) => None,
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_tracker_basic() {
144        let events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>> =
145            Rc::new(Default::default());
146
147        // Add some events
148        {
149            let mut events_mut = events.borrow_mut();
150            events_mut.insert(
151                RowIndex::from(1),
152                vec![DebugVarInfo::new("x", DebugVarLocation::Stack(0))],
153            );
154            events_mut.insert(
155                RowIndex::from(5),
156                vec![DebugVarInfo::new("y", DebugVarLocation::Stack(1))],
157            );
158        }
159
160        let mut tracker = DebugVarTracker::new(events);
161
162        // Initially no variables
163        assert_eq!(tracker.variable_count(), 0);
164
165        // Process up to cycle 3
166        tracker.update_to_cycle(RowIndex::from(3));
167        assert_eq!(tracker.variable_count(), 1);
168        assert!(tracker.get_variable("x").is_some());
169        assert!(tracker.get_variable("y").is_none());
170
171        // Process up to cycle 10
172        tracker.update_to_cycle(RowIndex::from(10));
173        assert_eq!(tracker.variable_count(), 2);
174        assert!(tracker.get_variable("x").is_some());
175        assert!(tracker.get_variable("y").is_some());
176
177        // Verify resolve_variable_value resolves stack values
178        let x_snapshot = tracker.get_variable("x").unwrap();
179        let value = resolve_variable_value(
180            x_snapshot.info.value_location(),
181            &[Felt::new(42)],
182            |_| None,
183            |_| None,
184        );
185        assert_eq!(value, Some(Felt::new(42)));
186    }
187}