use std::{cell::RefCell, collections::BTreeMap, rc::Rc};
use miden_core::{
Felt,
operations::{DebugVarInfo, DebugVarLocation},
};
use miden_processor::trace::RowIndex;
const FRAME_BASE_LOCAL_MARKER: u32 = 1 << 31;
fn decode_frame_base_local_offset(encoded: u32) -> Option<i16> {
if encoded & FRAME_BASE_LOCAL_MARKER == 0 {
return None;
}
let low_bits = (encoded & 0xffff) as u16;
Some(i16::from_le_bytes(low_bits.to_le_bytes()))
}
#[derive(Debug, Clone)]
pub struct DebugVarSnapshot {
pub clk: RowIndex,
pub info: DebugVarInfo,
}
pub struct DebugVarTracker {
events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>>,
current_vars: BTreeMap<String, DebugVarSnapshot>,
processed_up_to: RowIndex,
}
impl DebugVarTracker {
pub fn new(events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>>) -> Self {
Self {
events,
current_vars: BTreeMap::new(),
processed_up_to: RowIndex::from(0),
}
}
pub fn record_events(&self, clk: RowIndex, infos: Vec<DebugVarInfo>) {
if !infos.is_empty() {
self.events.borrow_mut().entry(clk).or_default().extend(infos);
}
}
pub fn update_to_cycle(&mut self, clk: RowIndex) {
let events = self.events.borrow();
for (event_clk, var_infos) in events.range(self.processed_up_to..=clk) {
for info in var_infos {
let snapshot = DebugVarSnapshot {
clk: *event_clk,
info: info.clone(),
};
self.current_vars.insert(info.name().to_string(), snapshot);
}
}
self.processed_up_to = clk;
}
pub fn reset(&mut self) {
self.current_vars.clear();
self.processed_up_to = RowIndex::from(0);
}
pub fn current_variables(&self) -> impl Iterator<Item = &DebugVarSnapshot> {
self.current_vars.values()
}
pub fn get_variable(&self, name: &str) -> Option<&DebugVarSnapshot> {
self.current_vars.get(name)
}
pub fn variable_count(&self) -> usize {
self.current_vars.len()
}
pub fn has_variables(&self) -> bool {
!self.current_vars.is_empty()
}
}
pub fn resolve_variable_value(
location: &DebugVarLocation,
stack: &[Felt],
get_memory: impl Fn(u32) -> Option<Felt>,
get_local: impl Fn(i16) -> Option<Felt>,
) -> Option<Felt> {
match location {
DebugVarLocation::Stack(pos) => stack.get(*pos as usize).copied(),
DebugVarLocation::Memory(addr) => get_memory(*addr),
DebugVarLocation::Const(felt) => Some(*felt),
DebugVarLocation::Local(offset) => get_local(*offset),
DebugVarLocation::FrameBase {
global_index,
byte_offset,
} => {
if let Some(local_offset) = decode_frame_base_local_offset(*global_index) {
let base = get_local(local_offset)?;
let byte_addr = base.as_canonical_u64() as i64 + byte_offset;
let elem_addr = u32::try_from(byte_addr / 4).ok()?;
return get_memory(elem_addr);
}
let sp_elem_addr = *global_index / 4;
let base = get_memory(sp_elem_addr)?;
let byte_addr = base.as_canonical_u64() as i64 + byte_offset;
let elem_addr = (byte_addr / 4) as u32;
get_memory(elem_addr)
}
DebugVarLocation::Expression(_) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tracker_basic() {
let events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>> =
Rc::new(Default::default());
{
let mut events_mut = events.borrow_mut();
events_mut.insert(
RowIndex::from(1),
vec![DebugVarInfo::new("x", DebugVarLocation::Stack(0))],
);
events_mut.insert(
RowIndex::from(5),
vec![DebugVarInfo::new("y", DebugVarLocation::Stack(1))],
);
}
let mut tracker = DebugVarTracker::new(events);
assert_eq!(tracker.variable_count(), 0);
tracker.update_to_cycle(RowIndex::from(3));
assert_eq!(tracker.variable_count(), 1);
assert!(tracker.get_variable("x").is_some());
assert!(tracker.get_variable("y").is_none());
tracker.update_to_cycle(RowIndex::from(10));
assert_eq!(tracker.variable_count(), 2);
assert!(tracker.get_variable("x").is_some());
assert!(tracker.get_variable("y").is_some());
let x_snapshot = tracker.get_variable("x").unwrap();
let value = resolve_variable_value(
x_snapshot.info.value_location(),
&[Felt::new(42)],
|_| None,
|_| None,
);
assert_eq!(value, Some(Felt::new(42)));
}
}