miden_assembly_syntax/ast/instruction/
debug_var.rs1use alloc::{format, sync::Arc, vec::Vec};
2use core::{fmt, num::NonZeroU32};
3
4use miden_core::serde::{
5 ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, read_bounded_len,
6};
7use miden_debug_types::Location;
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11use crate::{
12 Felt,
13 ast::{TypeExpr, types::Type},
14};
15
16#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct DebugVarInfo {
25 name: Arc<str>,
27 ty: Option<Type>,
29 declared_type: Option<Arc<TypeExpr>>,
31 arg_index: Option<NonZeroU32>,
33 location: Option<Location>,
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 ty: None,
47 declared_type: None,
48 arg_index: None,
49 location: None,
50 value_location,
51 }
52 }
53
54 pub fn name(&self) -> &Arc<str> {
56 &self.name
57 }
58
59 pub fn ty(&self) -> Option<&Type> {
61 self.ty.as_ref()
62 }
63
64 pub fn declared_type(&self) -> Option<Arc<TypeExpr>> {
66 self.declared_type.clone()
67 }
68
69 pub fn set_ty(&mut self, ty: Type, declared_type: Option<Arc<TypeExpr>>) {
71 self.ty = Some(ty);
72 self.declared_type = declared_type;
73 }
74
75 pub fn arg_index(&self) -> Option<NonZeroU32> {
78 self.arg_index
79 }
80
81 pub fn set_arg_index(&mut self, arg_index: u32) {
86 self.arg_index =
87 Some(NonZeroU32::new(arg_index).expect("argument index must be 1-based (non-zero)"));
88 }
89
90 pub fn location(&self) -> Option<&Location> {
93 self.location.as_ref()
94 }
95
96 pub fn set_location(&mut self, location: Location) {
100 self.location = Some(location);
101 }
102
103 pub fn value_location(&self) -> &DebugVarLocation {
105 &self.value_location
106 }
107
108 pub fn set_value_location(&mut self, value_location: DebugVarLocation) {
110 self.value_location = value_location;
111 }
112}
113
114impl fmt::Display for DebugVarInfo {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 write!(f, "var.{}", self.name)?;
117
118 if let Some(arg_index) = self.arg_index {
119 write!(f, "[arg{arg_index}]")?;
120 }
121
122 write!(f, " = {}", self.value_location)?;
123
124 if let Some(loc) = &self.location {
125 write!(f, " [{}@{}..{}]", loc.uri, loc.start, loc.end)?;
126 }
127
128 Ok(())
129 }
130}
131
132#[derive(Clone, Debug, Eq, PartialEq)]
141#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
142pub enum DebugVarLocation {
143 Stack(u8),
145 Memory(u32),
147 Const(Felt),
149 Local(i16),
155 FrameBase {
162 global_index: u32,
164 byte_offset: i64,
166 },
167 Expression(Vec<u8>),
171}
172
173impl fmt::Display for DebugVarLocation {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 match self {
176 Self::Stack(pos) => write!(f, "stack[{pos}]"),
177 Self::Memory(addr) => write!(f, "mem[{addr}]"),
178 Self::Const(val) => write!(f, "const({})", val.as_canonical_u64()),
179 Self::Local(offset) => write!(f, "FMP{offset:+}"),
180 Self::FrameBase { global_index, byte_offset } => {
181 write!(f, "global[{global_index}]{byte_offset:+}")
182 },
183 Self::Expression(bytes) => {
184 write!(f, "expr(")?;
185 for (i, byte) in bytes.iter().enumerate() {
186 if i > 0 {
187 write!(f, " ")?;
188 }
189 write!(f, "{byte:02x}")?;
190 }
191 write!(f, ")")
192 },
193 }
194 }
195}
196
197impl Serializable for DebugVarLocation {
201 fn write_into<W: ByteWriter>(&self, target: &mut W) {
202 match self {
203 Self::Stack(pos) => {
204 target.write_u8(0);
205 target.write_u8(*pos);
206 },
207 Self::Memory(addr) => {
208 target.write_u8(1);
209 target.write_u32(*addr);
210 },
211 Self::Const(felt) => {
212 target.write_u8(2);
213 target.write_u64(felt.as_canonical_u64());
214 },
215 Self::Local(offset) => {
216 target.write_u8(3);
217 target.write_bytes(&offset.to_le_bytes());
218 },
219 Self::Expression(bytes) => {
220 target.write_u8(4);
221 bytes.write_into(target);
222 },
223 Self::FrameBase { global_index, byte_offset } => {
224 target.write_u8(5);
225 target.write_u32(*global_index);
226 target.write_bytes(&byte_offset.to_le_bytes());
227 },
228 }
229 }
230}
231
232impl Deserializable for DebugVarLocation {
233 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
234 let tag = source.read_u8()?;
235 match tag {
236 0 => Ok(Self::Stack(source.read_u8()?)),
237 1 => Ok(Self::Memory(source.read_u32()?)),
238 2 => {
239 let value = source.read_u64()?;
240 Ok(Self::Const(Felt::new_unchecked(value)))
241 },
242 3 => {
243 let bytes = source.read_array::<2>()?;
244 Ok(Self::Local(i16::from_le_bytes(bytes)))
245 },
246 4 => {
247 let bytes = read_bounded_bytes(source, "debug variable expression bytes")?;
248 Ok(Self::Expression(bytes))
249 },
250 5 => {
251 let global_index = source.read_u32()?;
252 let bytes = source.read_array::<8>()?;
253 let byte_offset = i64::from_le_bytes(bytes);
254 Ok(Self::FrameBase { global_index, byte_offset })
255 },
256 _ => Err(DeserializationError::InvalidValue(format!(
257 "invalid DebugVarLocation tag: {tag}"
258 ))),
259 }
260 }
261
262 fn min_serialized_size() -> usize {
263 u8::min_serialized_size() + u8::min_serialized_size()
265 }
266}
267
268fn read_bounded_bytes<R: ByteReader>(
269 source: &mut R,
270 label: &str,
271) -> Result<Vec<u8>, DeserializationError> {
272 let len = read_bounded_len(source, label, 1)?;
273 source.read_slice(len).map(<[u8]>::to_vec)
274}
275
276#[cfg(test)]
277mod tests {
278 use alloc::string::ToString;
279
280 use miden_core::serde::{Deserializable, Serializable, SliceReader};
281 use miden_debug_types::{ByteIndex, Uri};
282
283 use super::*;
284
285 #[test]
286 fn debug_var_info_display_simple() {
287 let var = DebugVarInfo::new("x", DebugVarLocation::Stack(0));
288 assert_eq!(var.to_string(), "var.x = stack[0]");
289 }
290
291 #[test]
292 fn debug_var_location_rejects_oversized_expression_length() {
293 let bytes = [4, 0x08, 0x2a, 0xfe, 0xfe, 0x01];
294 let mut reader = SliceReader::new(&bytes);
295 let err = DebugVarLocation::read_from(&mut reader).unwrap_err();
296 let DeserializationError::InvalidValue(message) = err else {
297 panic!("expected InvalidValue error");
298 };
299 assert!(message.contains("debug variable expression bytes count"));
300 assert!(message.contains("exceeds remaining input"));
301 }
302
303 #[test]
304 fn debug_var_info_display_with_arg() {
305 let mut var = DebugVarInfo::new("param", DebugVarLocation::Stack(2));
306 var.set_arg_index(1);
307 assert_eq!(var.to_string(), "var.param[arg1] = stack[2]");
308 }
309
310 #[test]
311 fn debug_var_info_display_with_location() {
312 let mut var = DebugVarInfo::new("y", DebugVarLocation::Memory(100));
313 var.set_location(Location::new(
314 Uri::new("test.rs"),
315 ByteIndex::from(0u32),
316 ByteIndex::from(5u32),
317 ));
318 assert_eq!(var.to_string(), "var.y = mem[100] [test.rs@0..5]");
319 }
320
321 #[test]
322 fn debug_var_location_display() {
323 assert_eq!(DebugVarLocation::Stack(0).to_string(), "stack[0]");
324 assert_eq!(DebugVarLocation::Memory(256).to_string(), "mem[256]");
325 assert_eq!(DebugVarLocation::Const(Felt::new_unchecked(42)).to_string(), "const(42)");
326 assert_eq!(DebugVarLocation::Local(-3).to_string(), "FMP-3");
327 assert_eq!(
328 DebugVarLocation::FrameBase { global_index: 20, byte_offset: -12 }.to_string(),
329 "global[20]-12"
330 );
331 assert_eq!(
332 DebugVarLocation::Expression(vec![0x10, 0x20, 0x30]).to_string(),
333 "expr(10 20 30)"
334 );
335 }
336
337 #[test]
338 fn debug_var_location_serialization_round_trip() {
339 let locations = [
340 DebugVarLocation::Stack(7),
341 DebugVarLocation::Memory(0xdead_beef),
342 DebugVarLocation::Const(Felt::new_unchecked(999)),
343 DebugVarLocation::Local(-3),
344 DebugVarLocation::FrameBase { global_index: 20, byte_offset: -12 },
345 DebugVarLocation::Expression(vec![0x10, 0x20, 0x30]),
346 ];
347
348 for loc in &locations {
349 let mut bytes = Vec::new();
350 loc.write_into(&mut bytes);
351 let mut reader = SliceReader::new(&bytes);
352 let deser = DebugVarLocation::read_from(&mut reader).unwrap();
353 assert_eq!(&deser, loc);
354 }
355 }
356
357 #[test]
358 fn debug_var_location_min_serialized_size_matches_shortest_variant() {
359 let locations = [DebugVarLocation::Stack(0), DebugVarLocation::Expression(Vec::new())];
360 let min_serialized_size = DebugVarLocation::min_serialized_size();
361
362 assert_eq!(min_serialized_size, 2);
363 for location in locations {
364 let mut bytes = Vec::new();
365 location.write_into(&mut bytes);
366 assert_eq!(bytes.len(), min_serialized_size);
367 }
368 }
369
370 #[test]
371 fn debug_var_info_set_value_location() {
372 let mut var = DebugVarInfo::new("x", DebugVarLocation::Stack(0));
373 var.set_value_location(DebugVarLocation::FrameBase { global_index: 20, byte_offset: -12 });
374 assert_eq!(
375 var.value_location(),
376 &DebugVarLocation::FrameBase { global_index: 20, byte_offset: -12 }
377 );
378 }
379}