Skip to main content

miden_debug_engine/debug/
variables.rs

1use alloc::{
2    collections::BTreeMap,
3    rc::Rc,
4    string::{String, ToString},
5    sync::Arc,
6    vec::Vec,
7};
8use core::cell::RefCell;
9
10use miden_assembly_syntax::ast::{
11    DebugFrameBase, DebugLocationExpression, DebugLocationExpressionOp, DebugVarInfo,
12    DebugVarLocation, types::Type,
13};
14use miden_core::Felt;
15use miden_processor::trace::RowIndex;
16
17type DebugVarEvents = Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>>;
18type CapturedDebugValues = Rc<RefCell<BTreeMap<(RowIndex, Arc<str>), Vec<Felt>>>>;
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: DebugVarEvents,
33    /// Canonical values frozen at transient debug decorators.
34    captured_values: CapturedDebugValues,
35    /// Current view of variables - maps variable name to most recent info.
36    current_vars: BTreeMap<String, DebugVarSnapshot>,
37    /// Canonical values captured for currently visible transient variables.
38    current_captured_values: BTreeMap<String, Vec<Felt>>,
39    /// The clock cycle up to which we've processed events.
40    processed_up_to: RowIndex,
41}
42
43impl DebugVarTracker {
44    /// Create a new tracker using the given shared event store.
45    pub fn new(events: DebugVarEvents) -> Self {
46        Self {
47            events,
48            captured_values: Rc::new(Default::default()),
49            current_vars: BTreeMap::new(),
50            current_captured_values: BTreeMap::new(),
51            processed_up_to: RowIndex::from(0),
52        }
53    }
54
55    /// Record debug variable events at the given clock cycle.
56    pub fn record_events(&self, clk: RowIndex, infos: Vec<DebugVarInfo>) {
57        if !infos.is_empty() {
58            self.events.borrow_mut().entry(clk).or_default().extend(infos);
59        }
60    }
61
62    /// Record debug variable events after freezing transient stack-backed values.
63    pub fn record_events_with_stack(
64        &self,
65        clk: RowIndex,
66        mut infos: Vec<DebugVarInfo>,
67        stack: &[Felt],
68    ) {
69        let names = infos.iter().map(|info| info.name().clone()).collect::<Vec<_>>();
70        let captured_values = snapshot_transient_debug_values(&mut infos, stack);
71        {
72            let mut stored_values = self.captured_values.borrow_mut();
73            for name in names {
74                stored_values.remove(&(clk, name));
75            }
76            stored_values
77                .extend(captured_values.into_iter().map(|(name, values)| ((clk, name), values)));
78        }
79        self.record_events(clk, infos);
80    }
81
82    /// Process all events up to and including `clk`, updating current variable state.
83    pub fn update_to_cycle(&mut self, clk: RowIndex) {
84        let events = self.events.borrow();
85        let captured_values = self.captured_values.borrow();
86
87        // Process events from processed_up_to to clk
88        for (event_clk, var_infos) in events.range(self.processed_up_to..=clk) {
89            for info in var_infos {
90                if is_debug_var_kill(info) {
91                    self.current_vars.remove(info.name().as_ref());
92                    self.current_captured_values.remove(info.name().as_ref());
93                    continue;
94                }
95                let snapshot = DebugVarSnapshot {
96                    clk: *event_clk,
97                    info: info.clone(),
98                };
99                let name = info.name().to_string();
100                match captured_values.get(&(*event_clk, info.name().clone())) {
101                    Some(values) => {
102                        self.current_captured_values.insert(name.clone(), values.clone());
103                    }
104                    None => {
105                        self.current_captured_values.remove(&name);
106                    }
107                }
108                self.current_vars.insert(name, snapshot);
109            }
110        }
111
112        self.processed_up_to = clk;
113    }
114
115    /// Reset the tracker to the beginning of execution.
116    pub fn reset(&mut self) {
117        self.current_vars.clear();
118        self.current_captured_values.clear();
119        self.processed_up_to = RowIndex::from(0);
120    }
121
122    /// Get all currently visible variables.
123    pub fn current_variables(&self) -> impl Iterator<Item = &DebugVarSnapshot> {
124        self.current_vars.values()
125    }
126
127    /// Get a specific variable by name.
128    pub fn get_variable(&self, name: &str) -> Option<&DebugVarSnapshot> {
129        self.current_vars.get(name)
130    }
131
132    /// Returns canonical values captured at the variable's latest debug decorator.
133    pub fn captured_values(&self, name: &str) -> Option<&[Felt]> {
134        self.current_captured_values.get(name).map(Vec::as_slice)
135    }
136
137    /// Get the number of tracked variables.
138    pub fn variable_count(&self) -> usize {
139        self.current_vars.len()
140    }
141
142    /// Check if there are any tracked variables.
143    pub fn has_variables(&self) -> bool {
144        !self.current_vars.is_empty()
145    }
146}
147
148/// Snapshot transient debug locations at the decorator point.
149///
150/// Stack locations are only meaningful at the debug decorator itself. Keeping them live and
151/// resolving them against a later VM stack can report unrelated values. Memory, local, and
152/// frame-base declarations describe live storage and must be resolved against the current VM state
153/// when the user inspects variables.
154pub fn snapshot_transient_debug_values(
155    infos: &mut [DebugVarInfo],
156    stack: &[Felt],
157) -> BTreeMap<Arc<str>, Vec<Felt>> {
158    let mut captured_values = BTreeMap::new();
159
160    for info in infos {
161        captured_values.remove(info.name().as_ref());
162        match info.value_location() {
163            DebugVarLocation::Stack(position) => {
164                let count = info.ty().and_then(super::abi_types::value_felt_count).unwrap_or(1);
165                let start = *position as usize;
166                let values = start
167                    .checked_add(count)
168                    .and_then(|end| stack.get(start..end))
169                    .map(<[Felt]>::to_vec);
170                let location = values
171                    .as_ref()
172                    .and_then(|values| values.first().copied())
173                    .or_else(|| (count == 0).then(|| Felt::from_u32(0)))
174                    .map(DebugVarLocation::Const)
175                    .unwrap_or(DebugVarLocation::Unavailable);
176                if let Some(values) = values {
177                    captured_values.insert(info.name().clone(), values);
178                }
179                info.set_value_location(location);
180            }
181            DebugVarLocation::Expression(expression) => {
182                let operations = expression
183                    .operations()
184                    .iter()
185                    .map(|operation| match operation {
186                        DebugLocationExpressionOp::ReadStack(position) => stack
187                            .get(*position as usize)
188                            .map(|value| {
189                                DebugLocationExpressionOp::ConstU64(value.as_canonical_u64())
190                            })
191                            .ok_or(()),
192                        operation => Ok(*operation),
193                    })
194                    .collect::<Result<Vec<_>, _>>();
195                let location = operations
196                    .and_then(|ops| DebugLocationExpression::new(ops).map_err(|_| ()))
197                    .map(DebugVarLocation::Expression)
198                    .unwrap_or(DebugVarLocation::Unavailable);
199                info.set_value_location(location);
200            }
201            _ => {}
202        }
203    }
204
205    captured_values
206}
207
208fn is_debug_var_kill(info: &DebugVarInfo) -> bool {
209    matches!(info.value_location(), DebugVarLocation::Unavailable)
210}
211
212/// Resolve a debug variable's value given its location and the current VM state.
213pub fn resolve_variable_value(
214    location: &DebugVarLocation,
215    stack: &[Felt],
216    get_memory: impl Fn(u32) -> Option<Felt>,
217    get_local: impl Fn(i16) -> Option<Felt>,
218) -> Option<Felt> {
219    resolve_variable_values(location, 1, stack, get_memory, get_local)?.pop()
220}
221
222/// Resolve one or more consecutive felts for a debug variable.
223pub fn resolve_variable_values(
224    location: &DebugVarLocation,
225    count: usize,
226    stack: &[Felt],
227    get_memory: impl Fn(u32) -> Option<Felt>,
228    get_local: impl Fn(i16) -> Option<Felt>,
229) -> Option<Vec<Felt>> {
230    if count == 0 {
231        return Some(Vec::new());
232    }
233
234    match location {
235        DebugVarLocation::Stack(pos) => {
236            let start = *pos as usize;
237            let end = start.checked_add(count)?;
238            Some(stack.get(start..end)?.to_vec())
239        }
240        DebugVarLocation::Memory(addr) => resolve_consecutive_memory(*addr, count, &get_memory),
241        DebugVarLocation::Const(felt) => (count == 1).then_some(vec![*felt]),
242        DebugVarLocation::Local(offset) => {
243            let mut values = Vec::with_capacity(count);
244            for index in 0..count {
245                let index = i16::try_from(index).ok()?;
246                values.push(get_local(offset.checked_add(index)?)?);
247            }
248            Some(values)
249        }
250        DebugVarLocation::ResolvedFrameBase { base, byte_offset } => {
251            resolve_frame_base_values(*base, *byte_offset, count, &get_memory, &get_local)
252        }
253        DebugVarLocation::Expression(expression) => {
254            match resolve_expression(expression.operations(), stack, &get_memory, &get_local)? {
255                ResolvedExpression::Scalar(value) => {
256                    (count == 1).then(|| integer_to_felt(value)).flatten().map(|value| vec![value])
257                }
258                ResolvedExpression::ByteAddress(address) => {
259                    let element_address = u32::try_from(address / 4).ok()?;
260                    resolve_consecutive_memory(element_address, count, &get_memory)
261                }
262            }
263        }
264        DebugVarLocation::Unavailable => None,
265    }
266}
267
268/// Resolve a typed debug variable into the canonical ABI felts consumed by the typed decoder.
269///
270/// Simple Miden locations already contain canonical stack values. Frame-base locations and a
271/// terminal [`DebugLocationExpressionOp::DerefBytes`] instead identify packed Rust memory, which
272/// must be read at byte granularity and lifted according to `ty` before it can be decoded.
273pub fn resolve_typed_variable_values(
274    location: &DebugVarLocation,
275    ty: &Type,
276    count: usize,
277    stack: &[Felt],
278    get_memory: impl Fn(u32) -> Option<Felt>,
279    get_local: impl Fn(i16) -> Option<Felt>,
280) -> Option<Vec<Felt>> {
281    match location {
282        DebugVarLocation::ResolvedFrameBase { base, byte_offset } => {
283            let byte_address =
284                resolve_frame_base_address(*base, *byte_offset, &get_memory, &get_local)?;
285            resolve_typed_memory_value(ty, byte_address, count, &get_memory)
286        }
287        DebugVarLocation::Expression(expression) => {
288            match resolve_expression(expression.operations(), stack, &get_memory, &get_local)? {
289                ResolvedExpression::ByteAddress(byte_address) => {
290                    resolve_typed_memory_value(ty, byte_address, count, &get_memory)
291                }
292                ResolvedExpression::Scalar(value) => {
293                    (count == 1).then(|| integer_to_felt(value)).flatten().map(|value| vec![value])
294                }
295            }
296        }
297        _ => resolve_variable_values(location, count, stack, get_memory, get_local),
298    }
299}
300
301fn resolve_consecutive_memory(
302    start_addr: u32,
303    count: usize,
304    get_memory: &impl Fn(u32) -> Option<Felt>,
305) -> Option<Vec<Felt>> {
306    let mut values = Vec::with_capacity(count);
307    for index in 0..count {
308        let addr = start_addr.checked_add(u32::try_from(index).ok()?)?;
309        values.push(get_memory(addr)?);
310    }
311    Some(values)
312}
313
314#[derive(Clone, Copy, Debug, Eq, PartialEq)]
315enum ResolvedExpression {
316    Scalar(i128),
317    ByteAddress(u64),
318}
319
320fn resolve_expression(
321    ops: &[DebugLocationExpressionOp],
322    stack: &[Felt],
323    get_memory: &impl Fn(u32) -> Option<Felt>,
324    get_local: &impl Fn(i16) -> Option<Felt>,
325) -> Option<ResolvedExpression> {
326    let mut values = Vec::<i128>::new();
327
328    for (index, op) in ops.iter().enumerate() {
329        match op {
330            DebugLocationExpressionOp::ReadStack(index) => {
331                values.push(i128::from(stack.get(*index as usize)?.as_canonical_u64()));
332            }
333            DebugLocationExpressionOp::ReadMemory(index) => {
334                values.push(i128::from(get_memory(*index)?.as_canonical_u64()));
335            }
336            DebugLocationExpressionOp::ReadLocal(index) => {
337                values.push(i128::from(get_local(*index)?.as_canonical_u64()));
338            }
339            DebugLocationExpressionOp::ConstU64(value) => {
340                values.push(i128::from(*value));
341            }
342            DebugLocationExpressionOp::ConstI64(value) => {
343                values.push(i128::from(*value));
344            }
345            DebugLocationExpressionOp::AddUnsigned(value) => {
346                let lhs = values.pop()?;
347                values.push(lhs.checked_add(i128::from(*value))?);
348            }
349            DebugLocationExpressionOp::Add => {
350                let rhs = values.pop()?;
351                let lhs = values.pop()?;
352                values.push(lhs.checked_add(rhs)?);
353            }
354            DebugLocationExpressionOp::Sub => {
355                let rhs = values.pop()?;
356                let lhs = values.pop()?;
357                values.push(lhs.checked_sub(rhs)?);
358            }
359            DebugLocationExpressionOp::DerefBytes => {
360                let byte_address = u64::try_from(values.pop()?).ok()?;
361                if index + 1 == ops.len() {
362                    return Some(ResolvedExpression::ByteAddress(byte_address));
363                }
364                let element_address = u32::try_from(byte_address / 4).ok()?;
365                values.push(i128::from(get_memory(element_address)?.as_canonical_u64()));
366            }
367            DebugLocationExpressionOp::FrameBaseAddress { base, byte_offset } => {
368                values.push(i128::from(resolve_frame_base_address(
369                    *base,
370                    *byte_offset,
371                    get_memory,
372                    get_local,
373                )?));
374            }
375        }
376    }
377
378    values.pop().map(ResolvedExpression::Scalar)
379}
380
381fn resolve_frame_base_values(
382    base: DebugFrameBase,
383    byte_offset: i64,
384    count: usize,
385    get_memory: &impl Fn(u32) -> Option<Felt>,
386    get_local: &impl Fn(i16) -> Option<Felt>,
387) -> Option<Vec<Felt>> {
388    let byte_address = resolve_frame_base_address(base, byte_offset, get_memory, get_local)?;
389    resolve_byte_address_values(byte_address, count, get_memory)
390}
391
392fn resolve_frame_base_address(
393    base: DebugFrameBase,
394    byte_offset: i64,
395    get_memory: &impl Fn(u32) -> Option<Felt>,
396    get_local: &impl Fn(i16) -> Option<Felt>,
397) -> Option<u64> {
398    let base = match base {
399        DebugFrameBase::Local(offset) => get_local(offset)?,
400        DebugFrameBase::Memory(address) => get_memory(address)?,
401    };
402    base.as_canonical_u64().checked_add_signed(byte_offset)
403}
404
405fn resolve_byte_address_values(
406    byte_address: u64,
407    count: usize,
408    get_memory: &impl Fn(u32) -> Option<Felt>,
409) -> Option<Vec<Felt>> {
410    if !byte_address.is_multiple_of(4) {
411        return None;
412    }
413    let element_address = u32::try_from(byte_address / 4).ok()?;
414    resolve_consecutive_memory(element_address, count, get_memory)
415}
416
417fn integer_to_felt(value: i128) -> Option<Felt> {
418    Felt::new(u64::try_from(value).ok()?).ok()
419}
420
421fn read_memory_bytes(
422    byte_address: u64,
423    size: usize,
424    get_memory: &impl Fn(u32) -> Option<Felt>,
425) -> Option<Vec<u8>> {
426    if size == 0 {
427        return Some(Vec::new());
428    }
429
430    let element_address = u32::try_from(byte_address / 4).ok()?;
431    let byte_offset = usize::try_from(byte_address % 4).ok()?;
432    let end = byte_offset.checked_add(size)?;
433    let element_count = end.div_ceil(4);
434    let mut bytes = Vec::with_capacity(element_count.checked_mul(4)?);
435
436    for index in 0..element_count {
437        let address = element_address.checked_add(u32::try_from(index).ok()?)?;
438        let value = get_memory(address)?.as_canonical_u64() as u32;
439        bytes.extend_from_slice(&value.to_le_bytes());
440    }
441
442    Some(bytes.get(byte_offset..end)?.to_vec())
443}
444
445fn resolve_typed_memory_value(
446    ty: &Type,
447    byte_address: u64,
448    expected_count: usize,
449    get_memory: &impl Fn(u32) -> Option<Felt>,
450) -> Option<Vec<Felt>> {
451    let mut values = Vec::with_capacity(expected_count);
452    append_typed_memory_value(ty, byte_address, get_memory, &mut values)?;
453    (values.len() == expected_count).then_some(values)
454}
455
456fn append_typed_memory_value(
457    ty: &Type,
458    byte_address: u64,
459    get_memory: &impl Fn(u32) -> Option<Felt>,
460    values: &mut Vec<Felt>,
461) -> Option<()> {
462    match ty {
463        Type::Felt => {
464            if !byte_address.is_multiple_of(4) {
465                return None;
466            }
467            values.push(get_memory(u32::try_from(byte_address / 4).ok()?)?);
468        }
469        Type::I1 => {
470            let byte = *read_memory_bytes(byte_address, 1, get_memory)?.first()?;
471            values.push(Felt::from_u32(u32::from(byte & 1)));
472        }
473        Type::I8 | Type::I16 | Type::I32 | Type::I64 | Type::I128 => {
474            let value = read_unsigned_integer(ty, byte_address, get_memory)?;
475            let bit_width = u32::try_from(ty.size_in_bits()).ok()?;
476            let shift = 128_u32.checked_sub(bit_width)?;
477            let signed = ((value << shift) as i128) >> shift;
478            let slot_bits = u32::try_from(ty.size_in_felts().checked_mul(32)?).ok()?;
479            let canonical = (signed as u128) & low_bits_mask(slot_bits);
480            append_u128_limbs(canonical, ty.size_in_felts(), values);
481        }
482        Type::U8 | Type::U16 | Type::U32 | Type::U64 | Type::U128 | Type::F64 => {
483            let value = read_unsigned_integer(ty, byte_address, get_memory)?;
484            append_u128_limbs(value, ty.size_in_felts(), values);
485        }
486        Type::Struct(struct_ty) => {
487            for field in struct_ty.get().fields() {
488                let field_address = byte_address.checked_add(u64::from(field.offset))?;
489                append_typed_memory_value(&field.ty, field_address, get_memory, values)?;
490            }
491        }
492        Type::Array(array_ty) => {
493            let element_size = array_ty.ty.size_in_bytes();
494            let alignment = array_ty.ty.min_alignment();
495            let stride = align_up(element_size, alignment)?;
496            for index in 0..array_ty.len {
497                let offset = index.checked_mul(stride)?;
498                let element_address = byte_address.checked_add(u64::try_from(offset).ok()?)?;
499                append_typed_memory_value(&array_ty.ty, element_address, get_memory, values)?;
500            }
501        }
502        Type::U256
503        | Type::List(_)
504        | Type::Ptr(_)
505        | Type::Function(_)
506        | Type::Enum(_)
507        | Type::Unknown
508        | Type::Never
509        | Type::Variadic => return None,
510    }
511
512    Some(())
513}
514
515fn read_unsigned_integer(
516    ty: &Type,
517    byte_address: u64,
518    get_memory: &impl Fn(u32) -> Option<Felt>,
519) -> Option<u128> {
520    let bytes = read_memory_bytes(byte_address, ty.size_in_bytes(), get_memory)?;
521    let mut value = 0_u128;
522    for (index, byte) in bytes.into_iter().enumerate() {
523        value |= u128::from(byte) << index.checked_mul(8)?;
524    }
525    Some(value)
526}
527
528fn append_u128_limbs(value: u128, count: usize, values: &mut Vec<Felt>) {
529    values.extend((0..count).map(|index| Felt::from_u32((value >> (index * 32)) as u32)));
530}
531
532fn low_bits_mask(bits: u32) -> u128 {
533    if bits == 128 {
534        u128::MAX
535    } else {
536        (1_u128 << bits) - 1
537    }
538}
539
540fn align_up(size: usize, alignment: usize) -> Option<usize> {
541    if size == 0 {
542        return Some(0);
543    }
544    size.checked_add(alignment.checked_sub(1)?)?
545        .checked_div(alignment)?
546        .checked_mul(alignment)
547}
548
549#[cfg(test)]
550mod tests {
551    use alloc::sync::Arc;
552
553    use miden_assembly_syntax::ast::types::{StructType, TypeRepr};
554
555    use super::*;
556
557    #[test]
558    fn test_tracker_basic() {
559        let events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>> =
560            Rc::new(Default::default());
561
562        // Add some events
563        {
564            let mut events_mut = events.borrow_mut();
565            events_mut.insert(
566                RowIndex::from(1),
567                vec![DebugVarInfo::new("x", DebugVarLocation::Stack(0))],
568            );
569            events_mut.insert(
570                RowIndex::from(5),
571                vec![DebugVarInfo::new("y", DebugVarLocation::Stack(1))],
572            );
573        }
574
575        let mut tracker = DebugVarTracker::new(events);
576
577        // Initially no variables
578        assert_eq!(tracker.variable_count(), 0);
579
580        // Process up to cycle 3
581        tracker.update_to_cycle(RowIndex::from(3));
582        assert_eq!(tracker.variable_count(), 1);
583        assert!(tracker.get_variable("x").is_some());
584        assert!(tracker.get_variable("y").is_none());
585
586        // Process up to cycle 10
587        tracker.update_to_cycle(RowIndex::from(10));
588        assert_eq!(tracker.variable_count(), 2);
589        assert!(tracker.get_variable("x").is_some());
590        assert!(tracker.get_variable("y").is_some());
591
592        // Verify resolve_variable_value resolves stack values
593        let x_snapshot = tracker.get_variable("x").unwrap();
594        let value = resolve_variable_value(
595            x_snapshot.info.value_location(),
596            &[Felt::new(42).expect("value exceeds field modulus")],
597            |_| None,
598            |_| None,
599        );
600        assert_eq!(value, Some(Felt::new(42).expect("value exceeds field modulus")));
601    }
602
603    #[test]
604    fn snapshots_transient_stack_locations_as_constants() {
605        let mut infos = vec![
606            DebugVarInfo::new("a", DebugVarLocation::Stack(0)),
607            DebugVarInfo::new("b", DebugVarLocation::Local(-1)),
608            DebugVarInfo::new(
609                "c",
610                DebugVarLocation::Expression(
611                    DebugLocationExpression::new(vec![
612                        DebugLocationExpressionOp::ReadStack(0),
613                        DebugLocationExpressionOp::AddUnsigned(3),
614                    ])
615                    .unwrap(),
616                ),
617            ),
618            DebugVarInfo::new(
619                "missing",
620                DebugVarLocation::Expression(
621                    DebugLocationExpression::new(vec![DebugLocationExpressionOp::ReadStack(1)])
622                        .unwrap(),
623                ),
624            ),
625        ];
626
627        let captured_values = snapshot_transient_debug_values(
628            &mut infos,
629            &[Felt::new(7).expect("value exceeds field modulus")],
630        );
631
632        assert_eq!(
633            infos[0].value_location(),
634            &DebugVarLocation::Const(Felt::new(7).expect("value exceeds field modulus"))
635        );
636        assert_eq!(infos[1].value_location(), &DebugVarLocation::Local(-1));
637        assert_eq!(
638            infos[2].value_location(),
639            &DebugVarLocation::Expression(
640                DebugLocationExpression::new(vec![
641                    DebugLocationExpressionOp::ConstU64(7),
642                    DebugLocationExpressionOp::AddUnsigned(3)
643                ])
644                .unwrap()
645            )
646        );
647        assert_eq!(infos[3].value_location(), &DebugVarLocation::Unavailable);
648        assert_eq!(captured_values.get("a" as &str), Some(&vec![Felt::from_u32(7)]));
649    }
650
651    #[test]
652    fn snapshots_all_felts_for_typed_stack_locations() {
653        let events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>> =
654            Rc::new(Default::default());
655        let mut tracker = DebugVarTracker::new(events);
656        let mut info = DebugVarInfo::new("wide", DebugVarLocation::Stack(0));
657        info.set_ty(Type::U64, None);
658
659        tracker.record_events_with_stack(
660            RowIndex::from(1),
661            vec![info],
662            &[Felt::from_u32(7), Felt::from_u32(1)],
663        );
664        tracker.update_to_cycle(RowIndex::from(1));
665
666        let snapshot = tracker.get_variable("wide").unwrap();
667        assert_eq!(snapshot.info.value_location(), &DebugVarLocation::Const(Felt::from_u32(7)));
668        assert_eq!(
669            tracker.captured_values("wide"),
670            Some([Felt::from_u32(7), Felt::from_u32(1)].as_slice())
671        );
672    }
673
674    #[test]
675    fn resolves_explicit_frame_bases() {
676        let expected = Felt::new(4_294_967_303).unwrap();
677        for (base, memory_base) in
678            [(DebugFrameBase::Local(-7), false), (DebugFrameBase::Memory(9), true)]
679        {
680            let value = resolve_variable_value(
681                &DebugVarLocation::ResolvedFrameBase {
682                    base,
683                    byte_offset: 28,
684                },
685                &[],
686                |address| {
687                    if memory_base && address == 9 {
688                        Some(Felt::new(1_048_528).unwrap())
689                    } else if address == 262_139 {
690                        Some(expected)
691                    } else {
692                        None
693                    }
694                },
695                |offset| (!memory_base && offset == -7).then_some(Felt::new(1_048_528).unwrap()),
696            );
697            assert_eq!(value, Some(expected));
698        }
699    }
700
701    #[test]
702    fn resolves_structured_location_expressions() {
703        let expression = DebugLocationExpression::new(vec![
704            DebugLocationExpressionOp::FrameBaseAddress {
705                base: DebugFrameBase::Local(-2),
706                byte_offset: 4,
707            },
708            DebugLocationExpressionOp::AddUnsigned(8),
709            DebugLocationExpressionOp::DerefBytes,
710        ])
711        .unwrap();
712        let value = resolve_variable_value(
713            &DebugVarLocation::Expression(expression),
714            &[],
715            |address| (address == 27).then_some(Felt::new(13).unwrap()),
716            |offset| (offset == -2).then_some(Felt::new(96).unwrap()),
717        );
718
719        assert_eq!(value, Some(Felt::new(13).unwrap()));
720    }
721
722    #[test]
723    fn resolves_untyped_byte_dereferences_as_memory_elements() {
724        let expression = DebugLocationExpression::new(vec![
725            DebugLocationExpressionOp::ConstU64(1),
726            DebugLocationExpressionOp::DerefBytes,
727        ])
728        .unwrap();
729        let expected = Felt::new(4_294_967_303).unwrap();
730        let value = resolve_variable_value(
731            &DebugVarLocation::Expression(expression),
732            &[],
733            |address| (address == 0).then_some(expected),
734            |_| None,
735        );
736
737        assert_eq!(value, Some(expected));
738    }
739
740    #[test]
741    fn preserves_whole_felts_after_nonterminal_byte_dereferences() {
742        let expression = DebugLocationExpression::new(vec![
743            DebugLocationExpressionOp::ConstU64(1),
744            DebugLocationExpressionOp::DerefBytes,
745            DebugLocationExpressionOp::AddUnsigned(1),
746        ])
747        .unwrap();
748        let value = resolve_variable_value(
749            &DebugVarLocation::Expression(expression),
750            &[],
751            |address| (address == 0).then(|| Felt::new(4_294_967_303).unwrap()),
752            |_| None,
753        );
754
755        assert_eq!(value, Some(Felt::new(4_294_967_304).unwrap()));
756    }
757
758    #[test]
759    fn resolves_wide_typed_values_from_unaligned_byte_addresses() {
760        let expression = DebugLocationExpression::new(vec![
761            DebugLocationExpressionOp::ConstU64(3),
762            DebugLocationExpressionOp::DerefBytes,
763        ])
764        .unwrap();
765        let values = resolve_typed_variable_values(
766            &DebugVarLocation::Expression(expression),
767            &Type::U64,
768            2,
769            &[],
770            |address| match address {
771                0 => Some(Felt::from_u32(0x3322_11aa)),
772                1 => Some(Felt::from_u32(0x7766_5544)),
773                2 => Some(Felt::from_u32(0xbbaa_9988)),
774                _ => None,
775            },
776            |_| None,
777        );
778
779        assert_eq!(values, Some(vec![Felt::from_u32(0x6655_4433), Felt::from_u32(0xaa99_8877)]));
780    }
781
782    #[test]
783    fn lifts_packed_struct_fields_into_canonical_abi_felts() {
784        let packed = Type::from(StructType::new_with_repr(
785            TypeRepr::packed(1),
786            [(Arc::from("tiny"), Type::U8), (Arc::from("half"), Type::U16)],
787        ));
788        let values = resolve_typed_variable_values(
789            &DebugVarLocation::ResolvedFrameBase {
790                base: DebugFrameBase::Local(-1),
791                byte_offset: 0,
792            },
793            &packed,
794            2,
795            &[],
796            |address| (address == 0).then_some(Felt::from_u32(0x3322_11aa)),
797            |offset| (offset == -1).then_some(Felt::from_u32(1)),
798        );
799
800        assert_eq!(values, Some(vec![Felt::from_u32(0x11), Felt::from_u32(0x3322)]));
801        assert_eq!(
802            crate::debug::format_value(&packed, |count| {
803                resolve_typed_variable_values(
804                    &DebugVarLocation::ResolvedFrameBase {
805                        base: DebugFrameBase::Local(-1),
806                        byte_offset: 0,
807                    },
808                    &packed,
809                    count,
810                    &[],
811                    |address| (address == 0).then_some(Felt::from_u32(0x3322_11aa)),
812                    |offset| (offset == -1).then_some(Felt::from_u32(1)),
813                )
814            })
815            .as_deref(),
816            Some("{ tiny: 17, half: 13090 }")
817        );
818    }
819
820    #[test]
821    fn sign_extends_typed_integers_to_their_canonical_slots() {
822        let values = resolve_typed_variable_values(
823            &DebugVarLocation::ResolvedFrameBase {
824                base: DebugFrameBase::Local(-1),
825                byte_offset: 0,
826            },
827            &Type::I8,
828            1,
829            &[],
830            |address| (address == 0).then_some(Felt::from_u32(0x0000_ff00)),
831            |offset| (offset == -1).then_some(Felt::from_u32(1)),
832        );
833
834        assert_eq!(values, Some(vec![Felt::from_u32(u32::MAX)]));
835    }
836
837    #[test]
838    fn rejects_invalid_location_expression_results() {
839        for expression in [
840            DebugLocationExpression::new(vec![DebugLocationExpressionOp::ConstI64(-1)]).unwrap(),
841            DebugLocationExpression::new(vec![
842                DebugLocationExpressionOp::ConstU64(u64::MAX),
843                DebugLocationExpressionOp::ConstU64(1),
844                DebugLocationExpressionOp::Add,
845            ])
846            .unwrap(),
847        ] {
848            assert_eq!(
849                resolve_variable_value(
850                    &DebugVarLocation::Expression(expression),
851                    &[],
852                    |_| None,
853                    |_| None,
854                ),
855                None
856            );
857        }
858    }
859
860    #[test]
861    fn resolves_consecutive_memory_values() {
862        let values = resolve_variable_values(
863            &DebugVarLocation::Memory(10),
864            2,
865            &[],
866            |address| match address {
867                10 => Some(Felt::new(1).unwrap()),
868                11 => Some(Felt::new(2).unwrap()),
869                _ => None,
870            },
871            |_| None,
872        );
873
874        assert_eq!(values, Some(vec![Felt::new(1).unwrap(), Felt::new(2).unwrap()]));
875    }
876
877    #[test]
878    fn debug_kill_removes_current_variable() {
879        let events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>> =
880            Rc::new(Default::default());
881        {
882            let mut events = events.borrow_mut();
883            events.insert(
884                RowIndex::from(1),
885                vec![DebugVarInfo::new(
886                    "x",
887                    DebugVarLocation::Const(Felt::new(1).expect("value exceeds field modulus")),
888                )],
889            );
890            events.insert(
891                RowIndex::from(2),
892                vec![DebugVarInfo::new("x", DebugVarLocation::Unavailable)],
893            );
894        }
895
896        let mut tracker = DebugVarTracker::new(events);
897        tracker.update_to_cycle(RowIndex::from(1));
898        assert!(tracker.get_variable("x").is_some());
899
900        tracker.update_to_cycle(RowIndex::from(2));
901        assert!(tracker.get_variable("x").is_none());
902    }
903}