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#[derive(Debug, Clone)]
22pub struct DebugVarSnapshot {
23 pub clk: RowIndex,
25 pub info: DebugVarInfo,
27}
28
29pub struct DebugVarTracker {
31 events: DebugVarEvents,
33 captured_values: CapturedDebugValues,
35 current_vars: BTreeMap<String, DebugVarSnapshot>,
37 current_captured_values: BTreeMap<String, Vec<Felt>>,
39 processed_up_to: RowIndex,
41}
42
43impl DebugVarTracker {
44 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 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 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 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 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 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 pub fn current_variables(&self) -> impl Iterator<Item = &DebugVarSnapshot> {
124 self.current_vars.values()
125 }
126
127 pub fn get_variable(&self, name: &str) -> Option<&DebugVarSnapshot> {
129 self.current_vars.get(name)
130 }
131
132 pub fn captured_values(&self, name: &str) -> Option<&[Felt]> {
134 self.current_captured_values.get(name).map(Vec::as_slice)
135 }
136
137 pub fn variable_count(&self) -> usize {
139 self.current_vars.len()
140 }
141
142 pub fn has_variables(&self) -> bool {
144 !self.current_vars.is_empty()
145 }
146}
147
148pub 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
212pub 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
222pub 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
268pub 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 {
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 assert_eq!(tracker.variable_count(), 0);
579
580 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 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 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}