miden_core/operations/debug_metadata/
debug_var.rs1use alloc::{format, sync::Arc, vec::Vec};
2use core::{fmt, num::NonZeroU32};
3
4use miden_debug_types::FileLineCol;
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 Felt,
10 serde::{
11 ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
12 read_bounded_len,
13 },
14};
15
16#[derive(Clone, Debug, Eq, PartialEq)]
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25pub struct DebugVarInfo {
26 #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_arc_str"))]
28 name: Arc<str>,
29 type_id: Option<u32>,
31 arg_index: Option<NonZeroU32>,
33 location: Option<FileLineCol>,
37 value_location: DebugVarLocation,
39}
40
41impl DebugVarInfo {
42 pub fn new(name: impl Into<Arc<str>>, value_location: DebugVarLocation) -> Self {
44 Self {
45 name: name.into(),
46 type_id: None,
47 arg_index: None,
48 location: None,
49 value_location,
50 }
51 }
52
53 pub fn name(&self) -> &str {
55 &self.name
56 }
57
58 pub fn type_id(&self) -> Option<u32> {
60 self.type_id
61 }
62
63 pub fn set_type_id(&mut self, type_id: u32) {
65 self.type_id = Some(type_id);
66 }
67
68 pub fn arg_index(&self) -> Option<NonZeroU32> {
71 self.arg_index
72 }
73
74 pub fn set_arg_index(&mut self, arg_index: u32) {
79 self.arg_index =
80 Some(NonZeroU32::new(arg_index).expect("argument index must be 1-based (non-zero)"));
81 }
82
83 pub fn location(&self) -> Option<&FileLineCol> {
86 self.location.as_ref()
87 }
88
89 pub fn set_location(&mut self, location: FileLineCol) {
93 self.location = Some(location);
94 }
95
96 pub fn value_location(&self) -> &DebugVarLocation {
98 &self.value_location
99 }
100
101 pub fn set_value_location(&mut self, value_location: DebugVarLocation) {
103 self.value_location = value_location;
104 }
105}
106
107#[cfg(feature = "serde")]
109fn deserialize_arc_str<'de, D>(deserializer: D) -> Result<Arc<str>, D::Error>
110where
111 D: serde::Deserializer<'de>,
112{
113 use alloc::string::String;
114 let s = String::deserialize(deserializer)?;
115 Ok(Arc::from(s))
116}
117
118impl fmt::Display for DebugVarInfo {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 write!(f, "var.{}", self.name)?;
121
122 if let Some(arg_index) = self.arg_index {
123 write!(f, "[arg{arg_index}]")?;
124 }
125
126 write!(f, " = {}", self.value_location)?;
127
128 if let Some(loc) = &self.location {
129 write!(f, " {loc}")?;
130 }
131
132 Ok(())
133 }
134}
135
136#[derive(Clone, Debug, Eq, PartialEq)]
145#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
146pub enum DebugVarLocation {
147 Stack(u8),
149 Memory(u32),
151 Const(Felt),
153 Local(i16),
159 FrameBase {
166 global_index: u32,
168 byte_offset: i64,
170 },
171 Expression(Vec<u8>),
175}
176
177impl fmt::Display for DebugVarLocation {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 match self {
180 Self::Stack(pos) => write!(f, "stack[{pos}]"),
181 Self::Memory(addr) => write!(f, "mem[{addr}]"),
182 Self::Const(val) => write!(f, "const({})", val.as_canonical_u64()),
183 Self::Local(offset) => write!(f, "FMP{offset:+}"),
184 Self::FrameBase { global_index, byte_offset } => {
185 write!(f, "global[{global_index}]{byte_offset:+}")
186 },
187 Self::Expression(bytes) => {
188 write!(f, "expr(")?;
189 for (i, byte) in bytes.iter().enumerate() {
190 if i > 0 {
191 write!(f, " ")?;
192 }
193 write!(f, "{byte:02x}")?;
194 }
195 write!(f, ")")
196 },
197 }
198 }
199}
200
201impl Serializable for DebugVarLocation {
205 fn write_into<W: ByteWriter>(&self, target: &mut W) {
206 match self {
207 Self::Stack(pos) => {
208 target.write_u8(0);
209 target.write_u8(*pos);
210 },
211 Self::Memory(addr) => {
212 target.write_u8(1);
213 target.write_u32(*addr);
214 },
215 Self::Const(felt) => {
216 target.write_u8(2);
217 target.write_u64(felt.as_canonical_u64());
218 },
219 Self::Local(offset) => {
220 target.write_u8(3);
221 target.write_bytes(&offset.to_le_bytes());
222 },
223 Self::Expression(bytes) => {
224 target.write_u8(4);
225 bytes.write_into(target);
226 },
227 Self::FrameBase { global_index, byte_offset } => {
228 target.write_u8(5);
229 target.write_u32(*global_index);
230 target.write_bytes(&byte_offset.to_le_bytes());
231 },
232 }
233 }
234}
235
236impl Deserializable for DebugVarLocation {
237 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
238 let tag = source.read_u8()?;
239 match tag {
240 0 => Ok(Self::Stack(source.read_u8()?)),
241 1 => Ok(Self::Memory(source.read_u32()?)),
242 2 => {
243 let value = source.read_u64()?;
244 Ok(Self::Const(Felt::new_unchecked(value)))
245 },
246 3 => {
247 let bytes = source.read_array::<2>()?;
248 Ok(Self::Local(i16::from_le_bytes(bytes)))
249 },
250 4 => {
251 let bytes = read_bounded_bytes(source, "debug variable expression bytes")?;
252 Ok(Self::Expression(bytes))
253 },
254 5 => {
255 let global_index = source.read_u32()?;
256 let bytes = source.read_array::<8>()?;
257 let byte_offset = i64::from_le_bytes(bytes);
258 Ok(Self::FrameBase { global_index, byte_offset })
259 },
260 _ => Err(DeserializationError::InvalidValue(format!(
261 "invalid DebugVarLocation tag: {tag}"
262 ))),
263 }
264 }
265}
266
267impl Serializable for DebugVarInfo {
268 fn write_into<W: ByteWriter>(&self, target: &mut W) {
269 (*self.name).write_into(target);
270 self.value_location.write_into(target);
271 self.type_id.write_into(target);
272 self.arg_index.map(core::num::NonZero::get).write_into(target);
273 self.location.write_into(target);
274 }
275}
276
277impl Deserializable for DebugVarInfo {
278 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
279 let name = read_bounded_string(source, "debug variable name bytes")?;
280 let value_location = DebugVarLocation::read_from(source)?;
281 let type_id = Option::<u32>::read_from(source)?;
282 let arg_index = Option::<u32>::read_from(source)?
283 .map(|n| {
284 NonZeroU32::new(n).ok_or_else(|| {
285 DeserializationError::InvalidValue("arg_index must be non-zero".into())
286 })
287 })
288 .transpose()?;
289 let location = Option::<FileLineCol>::read_from(source)?;
290
291 Ok(Self {
292 name,
293 type_id,
294 arg_index,
295 location,
296 value_location,
297 })
298 }
299}
300
301fn read_bounded_string<R: ByteReader>(
302 source: &mut R,
303 label: &str,
304) -> Result<Arc<str>, DeserializationError> {
305 let len = read_bounded_len(source, label, 1)?;
306 let bytes = source.read_slice(len)?;
307 let value = core::str::from_utf8(bytes)
308 .map_err(|err| DeserializationError::InvalidValue(format!("{err}")))?;
309 Ok(Arc::from(value))
310}
311
312fn read_bounded_bytes<R: ByteReader>(
313 source: &mut R,
314 label: &str,
315) -> Result<Vec<u8>, DeserializationError> {
316 let len = read_bounded_len(source, label, 1)?;
317 source.read_slice(len).map(<[u8]>::to_vec)
318}
319
320#[cfg(test)]
321mod tests {
322 use alloc::string::ToString;
323
324 use miden_debug_types::{ColumnNumber, LineNumber, Uri};
325
326 use super::*;
327 use crate::serde::{Deserializable, Serializable, SliceReader};
328
329 #[test]
330 fn debug_var_info_display_simple() {
331 let var = DebugVarInfo::new("x", DebugVarLocation::Stack(0));
332 assert_eq!(var.to_string(), "var.x = stack[0]");
333 }
334
335 #[test]
336 fn debug_var_info_rejects_oversized_name_length() {
337 let bytes = [0x08, 0x2a, 0xfe, 0xfe, 0x01];
338 let mut reader = SliceReader::new(&bytes);
339 let err = DebugVarInfo::read_from(&mut reader).unwrap_err();
340 let DeserializationError::InvalidValue(message) = err else {
341 panic!("expected InvalidValue error");
342 };
343 assert!(message.contains("debug variable name bytes count"));
344 assert!(message.contains("exceeds remaining input"));
345 }
346
347 #[test]
348 fn debug_var_location_rejects_oversized_expression_length() {
349 let bytes = [4, 0x08, 0x2a, 0xfe, 0xfe, 0x01];
350 let mut reader = SliceReader::new(&bytes);
351 let err = DebugVarLocation::read_from(&mut reader).unwrap_err();
352 let DeserializationError::InvalidValue(message) = err else {
353 panic!("expected InvalidValue error");
354 };
355 assert!(message.contains("debug variable expression bytes count"));
356 assert!(message.contains("exceeds remaining input"));
357 }
358
359 #[test]
360 fn debug_var_info_display_with_arg() {
361 let mut var = DebugVarInfo::new("param", DebugVarLocation::Stack(2));
362 var.set_arg_index(1);
363 assert_eq!(var.to_string(), "var.param[arg1] = stack[2]");
364 }
365
366 #[test]
367 fn debug_var_info_display_with_location() {
368 let mut var = DebugVarInfo::new("y", DebugVarLocation::Memory(100));
369 var.set_location(FileLineCol::new(
370 Uri::new("test.rs"),
371 LineNumber::new(42).unwrap(),
372 ColumnNumber::new(5).unwrap(),
373 ));
374 assert_eq!(var.to_string(), "var.y = mem[100] [test.rs@42:5]");
375 }
376
377 #[test]
378 fn debug_var_location_display() {
379 assert_eq!(DebugVarLocation::Stack(0).to_string(), "stack[0]");
380 assert_eq!(DebugVarLocation::Memory(256).to_string(), "mem[256]");
381 assert_eq!(DebugVarLocation::Const(Felt::new_unchecked(42)).to_string(), "const(42)");
382 assert_eq!(DebugVarLocation::Local(-3).to_string(), "FMP-3");
383 assert_eq!(
384 DebugVarLocation::FrameBase { global_index: 20, byte_offset: -12 }.to_string(),
385 "global[20]-12"
386 );
387 assert_eq!(
388 DebugVarLocation::Expression(vec![0x10, 0x20, 0x30]).to_string(),
389 "expr(10 20 30)"
390 );
391 }
392
393 #[test]
394 fn debug_var_location_serialization_round_trip() {
395 let locations = [
396 DebugVarLocation::Stack(7),
397 DebugVarLocation::Memory(0xdead_beef),
398 DebugVarLocation::Const(Felt::new_unchecked(999)),
399 DebugVarLocation::Local(-3),
400 DebugVarLocation::FrameBase { global_index: 20, byte_offset: -12 },
401 DebugVarLocation::Expression(vec![0x10, 0x20, 0x30]),
402 ];
403
404 for loc in &locations {
405 let mut bytes = Vec::new();
406 loc.write_into(&mut bytes);
407 let mut reader = SliceReader::new(&bytes);
408 let deser = DebugVarLocation::read_from(&mut reader).unwrap();
409 assert_eq!(&deser, loc);
410 }
411 }
412
413 #[test]
414 fn debug_var_info_serialization_round_trip_all_fields() {
415 let mut var = DebugVarInfo::new("full", DebugVarLocation::Expression(vec![0xaa, 0xbb]));
416 var.set_type_id(7);
417 var.set_arg_index(2);
418 var.set_location(FileLineCol::new(
419 Uri::new("lib.rs"),
420 LineNumber::new(50).unwrap(),
421 ColumnNumber::new(10).unwrap(),
422 ));
423
424 let mut bytes = Vec::new();
425 var.write_into(&mut bytes);
426 let mut reader = SliceReader::new(&bytes);
427 let deser = DebugVarInfo::read_from(&mut reader).unwrap();
428 assert_eq!(deser, var);
429 }
430
431 #[test]
432 fn debug_var_info_set_value_location() {
433 let mut var = DebugVarInfo::new("x", DebugVarLocation::Stack(0));
434 var.set_value_location(DebugVarLocation::FrameBase { global_index: 20, byte_offset: -12 });
435 assert_eq!(
436 var.value_location(),
437 &DebugVarLocation::FrameBase { global_index: 20, byte_offset: -12 }
438 );
439 }
440}