miden_debug_engine/debug/
variables.rs1use std::{cell::RefCell, collections::BTreeMap, rc::Rc};
2
3use miden_core::{
4 Felt,
5 operations::{DebugVarInfo, DebugVarLocation},
6 serde::{ByteReader, Deserializable, SliceReader},
7};
8use miden_processor::trace::RowIndex;
9
10const FRAME_BASE_LOCAL_MARKER: u32 = 1 << 31;
11const DEBUG_VAR_KILL_SENTINEL: &[u8] = b"\0miden.debug.kill";
12
13fn decode_frame_base_local_offset(encoded: u32) -> Option<i16> {
14 if encoded & FRAME_BASE_LOCAL_MARKER == 0 {
15 return None;
16 }
17
18 let low_bits = (encoded & 0xffff) as u16;
19 Some(i16::from_le_bytes(low_bits.to_le_bytes()))
20}
21
22#[derive(Debug, Clone)]
24pub struct DebugVarSnapshot {
25 pub clk: RowIndex,
27 pub info: DebugVarInfo,
29}
30
31pub struct DebugVarTracker {
33 events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>>,
35 current_vars: BTreeMap<String, DebugVarSnapshot>,
37 processed_up_to: RowIndex,
39}
40
41impl DebugVarTracker {
42 pub fn new(events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>>) -> Self {
44 Self {
45 events,
46 current_vars: BTreeMap::new(),
47 processed_up_to: RowIndex::from(0),
48 }
49 }
50
51 pub fn record_events(&self, clk: RowIndex, infos: Vec<DebugVarInfo>) {
53 if !infos.is_empty() {
54 self.events.borrow_mut().entry(clk).or_default().extend(infos);
55 }
56 }
57
58 pub fn update_to_cycle(&mut self, clk: RowIndex) {
60 let events = self.events.borrow();
61
62 for (event_clk, var_infos) in events.range(self.processed_up_to..=clk) {
64 for info in var_infos {
65 if is_debug_var_kill(info) {
66 self.current_vars.remove(info.name());
67 continue;
68 }
69 let snapshot = DebugVarSnapshot {
70 clk: *event_clk,
71 info: info.clone(),
72 };
73 self.current_vars.insert(info.name().to_string(), snapshot);
74 }
75 }
76
77 self.processed_up_to = clk;
78 }
79
80 pub fn reset(&mut self) {
82 self.current_vars.clear();
83 self.processed_up_to = RowIndex::from(0);
84 }
85
86 pub fn current_variables(&self) -> impl Iterator<Item = &DebugVarSnapshot> {
88 self.current_vars.values()
89 }
90
91 pub fn get_variable(&self, name: &str) -> Option<&DebugVarSnapshot> {
93 self.current_vars.get(name)
94 }
95
96 pub fn variable_count(&self) -> usize {
98 self.current_vars.len()
99 }
100
101 pub fn has_variables(&self) -> bool {
103 !self.current_vars.is_empty()
104 }
105}
106
107pub fn snapshot_transient_debug_values(infos: &mut [DebugVarInfo], stack: &[Felt]) {
114 for info in infos {
115 if let DebugVarLocation::Stack(pos) = info.value_location()
116 && let Some(value) = stack.get(*pos as usize).copied()
117 {
118 info.set_value_location(DebugVarLocation::Const(value));
119 }
120 }
121}
122
123fn is_debug_var_kill(info: &DebugVarInfo) -> bool {
124 matches!(
125 info.value_location(),
126 DebugVarLocation::Expression(expression) if expression == DEBUG_VAR_KILL_SENTINEL
127 )
128}
129
130pub fn resolve_variable_value(
132 location: &DebugVarLocation,
133 stack: &[Felt],
134 get_memory: impl Fn(u32) -> Option<Felt>,
135 get_local: impl Fn(i16) -> Option<Felt>,
136) -> Option<Felt> {
137 match location {
138 DebugVarLocation::Stack(pos) => stack.get(*pos as usize).copied(),
139 DebugVarLocation::Memory(addr) => get_memory(*addr),
140 DebugVarLocation::Const(felt) => Some(*felt),
141 DebugVarLocation::Local(offset) => get_local(*offset),
142 DebugVarLocation::FrameBase {
143 global_index,
144 byte_offset,
145 } => resolve_frame_base_value(*global_index, *byte_offset, &get_memory, &get_local),
146 DebugVarLocation::Expression(expression) => {
147 resolve_expression_value(expression, stack, &get_memory, &get_local)
148 }
149 }
150}
151
152fn resolve_frame_base_value(
153 global_index: u32,
154 byte_offset: i64,
155 get_memory: &impl Fn(u32) -> Option<Felt>,
156 get_local: &impl Fn(i16) -> Option<Felt>,
157) -> Option<Felt> {
158 if let Some(local_offset) = decode_frame_base_local_offset(global_index) {
159 let base = get_local(local_offset)?;
160 let byte_addr = base.as_canonical_u64() as i64 + byte_offset;
161 let elem_addr = byte_addr / 4;
162 let elem_addr = u32::try_from(elem_addr).ok()?;
163 return get_memory(elem_addr);
164 }
165
166 let sp_elem_addr = global_index / 4;
169 let base = get_memory(sp_elem_addr)?;
170 let byte_addr = base.as_canonical_u64() as i64 + byte_offset;
173 let elem_addr = (byte_addr / 4) as u32;
174 get_memory(elem_addr)
175}
176
177#[derive(Debug, Clone, PartialEq, Eq)]
178enum DebugExpressionOp {
179 WasmLocal(u32),
180 WasmGlobal(u32),
181 WasmStack(u32),
182 ConstU64(u64),
183 ConstS64(i64),
184 PlusUConst(u64),
185 Minus,
186 Plus,
187 Deref,
188 StackValue,
189 Piece,
190 BitPiece,
191 FrameBase { global_index: u32, byte_offset: i64 },
192 Address(u64),
193 Unsupported,
194}
195
196fn resolve_expression_value(
197 expression: &[u8],
198 stack: &[Felt],
199 get_memory: &impl Fn(u32) -> Option<Felt>,
200 get_local: &impl Fn(i16) -> Option<Felt>,
201) -> Option<Felt> {
202 let ops = read_expression(expression)?;
203 let mut values = Vec::<Felt>::new();
204
205 for op in ops {
206 match op {
207 DebugExpressionOp::WasmLocal(index) => {
208 values.push(get_local(i16::try_from(index).ok()?)?);
209 }
210 DebugExpressionOp::WasmStack(index) => {
211 values.push(stack.get(index as usize).copied()?);
212 }
213 DebugExpressionOp::ConstU64(value) => {
214 values.push(Felt::new(value).expect("value exceeds field modulus"));
215 }
216 DebugExpressionOp::ConstS64(value) => {
217 values.push(Felt::new(value as u64).expect("value exceeds field modulus"));
218 }
219 DebugExpressionOp::PlusUConst(value) => {
220 let lhs = values.pop()?;
221 values.push(
222 Felt::new(lhs.as_canonical_u64().wrapping_add(value))
223 .expect("value exceeds field modulus"),
224 );
225 }
226 DebugExpressionOp::Minus => {
227 let rhs = values.pop()?.as_canonical_u64();
228 let lhs = values.pop()?.as_canonical_u64();
229 values.push(Felt::new(lhs.wrapping_sub(rhs)).expect("value exceeds field modulus"));
230 }
231 DebugExpressionOp::Plus => {
232 let rhs = values.pop()?.as_canonical_u64();
233 let lhs = values.pop()?.as_canonical_u64();
234 values.push(Felt::new(lhs.wrapping_add(rhs)).expect("value exceeds field modulus"));
235 }
236 DebugExpressionOp::Deref => {
237 let addr = u32::try_from(values.pop()?.as_canonical_u64()).ok()?;
238 values.push(get_memory(addr)?);
239 }
240 DebugExpressionOp::StackValue => {}
241 DebugExpressionOp::FrameBase {
242 global_index,
243 byte_offset,
244 } => {
245 values.push(resolve_frame_base_value(
246 global_index,
247 byte_offset,
248 get_memory,
249 get_local,
250 )?);
251 }
252 DebugExpressionOp::Address(address) => {
253 values.push(Felt::new(address).expect("value exceeds field modulus"));
254 }
255 DebugExpressionOp::WasmGlobal(_)
256 | DebugExpressionOp::Piece
257 | DebugExpressionOp::BitPiece
258 | DebugExpressionOp::Unsupported => return None,
259 }
260 }
261
262 values.pop()
263}
264
265fn read_expression(expression: &[u8]) -> Option<Vec<DebugExpressionOp>> {
266 let mut reader = SliceReader::new(expression);
267 let len = usize::read_from(&mut reader).ok()?;
268 let mut ops = Vec::with_capacity(len);
269 for _ in 0..len {
270 ops.push(read_expression_op(&mut reader)?);
271 }
272 Some(ops)
273}
274
275fn read_expression_op(reader: &mut SliceReader<'_>) -> Option<DebugExpressionOp> {
276 Some(match reader.read_u8().ok()? {
277 0 => DebugExpressionOp::WasmLocal(u32::read_from(reader).ok()?),
278 1 => DebugExpressionOp::WasmGlobal(u32::read_from(reader).ok()?),
279 2 => DebugExpressionOp::WasmStack(u32::read_from(reader).ok()?),
280 3 => DebugExpressionOp::ConstU64(u64::read_from(reader).ok()?),
281 4 => DebugExpressionOp::ConstS64(u64::read_from(reader).ok()? as i64),
282 5 => DebugExpressionOp::PlusUConst(u64::read_from(reader).ok()?),
283 6 => DebugExpressionOp::Minus,
284 7 => DebugExpressionOp::Plus,
285 8 => DebugExpressionOp::Deref,
286 9 => DebugExpressionOp::StackValue,
287 10 => {
288 let _size = u64::read_from(reader).ok()?;
289 DebugExpressionOp::Piece
290 }
291 11 => {
292 let _size = u64::read_from(reader).ok()?;
293 let _offset = u64::read_from(reader).ok()?;
294 DebugExpressionOp::BitPiece
295 }
296 12 => {
297 let global_index = u32::read_from(reader).ok()?;
298 let byte_offset = u64::read_from(reader).ok()? as i64;
299 DebugExpressionOp::FrameBase {
300 global_index,
301 byte_offset,
302 }
303 }
304 13 => DebugExpressionOp::Address(u64::read_from(reader).ok()?),
305 u8::MAX => {
306 let len = usize::read_from(reader).ok()?;
307 let _name = reader.read_slice(len).ok()?;
308 DebugExpressionOp::Unsupported
309 }
310 _ => return None,
311 })
312}
313
314#[cfg(test)]
315mod tests {
316 use miden_core::serde::ByteWriter;
317
318 use super::*;
319
320 #[test]
321 fn test_tracker_basic() {
322 let events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>> =
323 Rc::new(Default::default());
324
325 {
327 let mut events_mut = events.borrow_mut();
328 events_mut.insert(
329 RowIndex::from(1),
330 vec![DebugVarInfo::new("x", DebugVarLocation::Stack(0))],
331 );
332 events_mut.insert(
333 RowIndex::from(5),
334 vec![DebugVarInfo::new("y", DebugVarLocation::Stack(1))],
335 );
336 }
337
338 let mut tracker = DebugVarTracker::new(events);
339
340 assert_eq!(tracker.variable_count(), 0);
342
343 tracker.update_to_cycle(RowIndex::from(3));
345 assert_eq!(tracker.variable_count(), 1);
346 assert!(tracker.get_variable("x").is_some());
347 assert!(tracker.get_variable("y").is_none());
348
349 tracker.update_to_cycle(RowIndex::from(10));
351 assert_eq!(tracker.variable_count(), 2);
352 assert!(tracker.get_variable("x").is_some());
353 assert!(tracker.get_variable("y").is_some());
354
355 let x_snapshot = tracker.get_variable("x").unwrap();
357 let value = resolve_variable_value(
358 x_snapshot.info.value_location(),
359 &[Felt::new(42).expect("value exceeds field modulus")],
360 |_| None,
361 |_| None,
362 );
363 assert_eq!(value, Some(Felt::new(42).expect("value exceeds field modulus")));
364 }
365
366 #[test]
367 fn snapshots_transient_stack_locations_as_constants() {
368 let mut infos = vec![
369 DebugVarInfo::new("a", DebugVarLocation::Stack(0)),
370 DebugVarInfo::new("b", DebugVarLocation::Local(-1)),
371 ];
372
373 snapshot_transient_debug_values(
374 &mut infos,
375 &[Felt::new(7).expect("value exceeds field modulus")],
376 );
377
378 assert_eq!(
379 infos[0].value_location(),
380 &DebugVarLocation::Const(Felt::new(7).expect("value exceeds field modulus"))
381 );
382 assert_eq!(infos[1].value_location(), &DebugVarLocation::Local(-1));
383 }
384
385 #[test]
386 fn resolves_local_frame_base_as_byte_address() {
387 let encoded =
388 FRAME_BASE_LOCAL_MARKER | u32::from(u16::from_le_bytes((-7i16).to_le_bytes()));
389
390 let value = resolve_variable_value(
391 &DebugVarLocation::FrameBase {
392 global_index: encoded,
393 byte_offset: 28,
394 },
395 &[],
396 |addr| (addr == 262_139).then_some(Felt::new(13).expect("value exceeds field modulus")),
397 |offset| {
398 (offset == -7).then_some(Felt::new(1_048_528).expect("value exceeds field modulus"))
399 },
400 );
401
402 assert_eq!(value, Some(Felt::new(13).expect("value exceeds field modulus")));
403 }
404
405 #[test]
406 fn debug_kill_removes_current_variable() {
407 let events: Rc<RefCell<BTreeMap<RowIndex, Vec<DebugVarInfo>>>> =
408 Rc::new(Default::default());
409 {
410 let mut events = events.borrow_mut();
411 events.insert(
412 RowIndex::from(1),
413 vec![DebugVarInfo::new(
414 "x",
415 DebugVarLocation::Const(Felt::new(1).expect("value exceeds field modulus")),
416 )],
417 );
418 events.insert(
419 RowIndex::from(2),
420 vec![DebugVarInfo::new(
421 "x",
422 DebugVarLocation::Expression(DEBUG_VAR_KILL_SENTINEL.to_vec()),
423 )],
424 );
425 }
426
427 let mut tracker = DebugVarTracker::new(events);
428 tracker.update_to_cycle(RowIndex::from(1));
429 assert!(tracker.get_variable("x").is_some());
430
431 tracker.update_to_cycle(RowIndex::from(2));
432 assert!(tracker.get_variable("x").is_none());
433 }
434
435 #[test]
436 fn resolves_const_stack_value_expression() {
437 let expression =
438 expression_bytes(&[TestExpressionOp::ConstU64(7), TestExpressionOp::StackValue]);
439
440 let value = resolve_variable_value(
441 &DebugVarLocation::Expression(expression),
442 &[],
443 |_| None,
444 |_| None,
445 );
446
447 assert_eq!(value, Some(Felt::new(7).expect("value exceeds field modulus")));
448 }
449
450 #[test]
451 fn resolves_local_stack_value_expression() {
452 let expression =
453 expression_bytes(&[TestExpressionOp::WasmLocal(0), TestExpressionOp::StackValue]);
454
455 let value = resolve_variable_value(
456 &DebugVarLocation::Expression(expression),
457 &[],
458 |_| None,
459 |offset| (offset == 0).then_some(Felt::new(11).expect("value exceeds field modulus")),
460 );
461
462 assert_eq!(value, Some(Felt::new(11).expect("value exceeds field modulus")));
463 }
464
465 enum TestExpressionOp {
466 WasmLocal(u32),
467 ConstU64(u64),
468 StackValue,
469 }
470
471 fn expression_bytes(ops: &[TestExpressionOp]) -> Vec<u8> {
472 let mut bytes = Vec::new();
473 bytes.write_usize(ops.len());
474 for op in ops {
475 match op {
476 TestExpressionOp::WasmLocal(index) => {
477 bytes.write_u8(0);
478 bytes.write_u32(*index);
479 }
480 TestExpressionOp::ConstU64(value) => {
481 bytes.write_u8(3);
482 bytes.write_u64(*value);
483 }
484 TestExpressionOp::StackValue => {
485 bytes.write_u8(9);
486 }
487 }
488 }
489 bytes
490 }
491}