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