miden_core/operations/decorators/
debug_var.rs1use alloc::{string::String, 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::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
11};
12
13#[derive(Clone, Debug, Eq, PartialEq)]
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22pub struct DebugVarInfo {
23 #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_arc_str"))]
25 name: Arc<str>,
26 type_id: Option<u32>,
28 arg_index: Option<NonZeroU32>,
30 location: Option<FileLineCol>,
34 value_location: DebugVarLocation,
36}
37
38impl DebugVarInfo {
39 pub fn new(name: impl Into<Arc<str>>, value_location: DebugVarLocation) -> Self {
41 Self {
42 name: name.into(),
43 type_id: None,
44 arg_index: None,
45 location: None,
46 value_location,
47 }
48 }
49
50 pub fn name(&self) -> &str {
52 &self.name
53 }
54
55 pub fn type_id(&self) -> Option<u32> {
57 self.type_id
58 }
59
60 pub fn set_type_id(&mut self, type_id: u32) {
62 self.type_id = Some(type_id);
63 }
64
65 pub fn arg_index(&self) -> Option<NonZeroU32> {
68 self.arg_index
69 }
70
71 pub fn set_arg_index(&mut self, arg_index: u32) {
76 self.arg_index =
77 Some(NonZeroU32::new(arg_index).expect("argument index must be 1-based (non-zero)"));
78 }
79
80 pub fn location(&self) -> Option<&FileLineCol> {
83 self.location.as_ref()
84 }
85
86 pub fn set_location(&mut self, location: FileLineCol) {
90 self.location = Some(location);
91 }
92
93 pub fn value_location(&self) -> &DebugVarLocation {
95 &self.value_location
96 }
97}
98
99#[cfg(feature = "serde")]
101fn deserialize_arc_str<'de, D>(deserializer: D) -> Result<Arc<str>, D::Error>
102where
103 D: serde::Deserializer<'de>,
104{
105 use alloc::string::String;
106 let s = String::deserialize(deserializer)?;
107 Ok(Arc::from(s))
108}
109
110impl fmt::Display for DebugVarInfo {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 write!(f, "var.{}", self.name)?;
113
114 if let Some(arg_index) = self.arg_index {
115 write!(f, "[arg{}]", arg_index)?;
116 }
117
118 write!(f, " = {}", self.value_location)?;
119
120 if let Some(loc) = &self.location {
121 write!(f, " {}", loc)?;
122 }
123
124 Ok(())
125 }
126}
127
128#[derive(Clone, Debug, Eq, PartialEq)]
137#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
138pub enum DebugVarLocation {
139 Stack(u8),
141 Memory(u32),
143 Const(Felt),
145 Local(i16),
151 Expression(Vec<u8>),
155}
156
157impl fmt::Display for DebugVarLocation {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 match self {
160 Self::Stack(pos) => write!(f, "stack[{}]", pos),
161 Self::Memory(addr) => write!(f, "mem[{}]", addr),
162 Self::Const(val) => write!(f, "const({})", val.as_canonical_u64()),
163 Self::Local(offset) => write!(f, "FMP{:+}", offset),
164 Self::Expression(bytes) => {
165 write!(f, "expr(")?;
166 for (i, byte) in bytes.iter().enumerate() {
167 if i > 0 {
168 write!(f, " ")?;
169 }
170 write!(f, "{:02x}", byte)?;
171 }
172 write!(f, ")")
173 },
174 }
175 }
176}
177
178impl Serializable for DebugVarLocation {
182 fn write_into<W: ByteWriter>(&self, target: &mut W) {
183 match self {
184 Self::Stack(pos) => {
185 target.write_u8(0);
186 target.write_u8(*pos);
187 },
188 Self::Memory(addr) => {
189 target.write_u8(1);
190 target.write_u32(*addr);
191 },
192 Self::Const(felt) => {
193 target.write_u8(2);
194 target.write_u64(felt.as_canonical_u64());
195 },
196 Self::Local(offset) => {
197 target.write_u8(3);
198 target.write_bytes(&offset.to_le_bytes());
199 },
200 Self::Expression(bytes) => {
201 target.write_u8(4);
202 bytes.write_into(target);
203 },
204 }
205 }
206}
207
208impl Deserializable for DebugVarLocation {
209 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
210 let tag = source.read_u8()?;
211 match tag {
212 0 => Ok(Self::Stack(source.read_u8()?)),
213 1 => Ok(Self::Memory(source.read_u32()?)),
214 2 => {
215 let value = source.read_u64()?;
216 Ok(Self::Const(Felt::new(value)))
217 },
218 3 => {
219 let bytes = source.read_array::<2>()?;
220 Ok(Self::Local(i16::from_le_bytes(bytes)))
221 },
222 4 => {
223 let bytes = Vec::<u8>::read_from(source)?;
224 Ok(Self::Expression(bytes))
225 },
226 _ => Err(DeserializationError::InvalidValue(format!(
227 "invalid DebugVarLocation tag: {tag}"
228 ))),
229 }
230 }
231}
232
233impl Serializable for DebugVarInfo {
234 fn write_into<W: ByteWriter>(&self, target: &mut W) {
235 (*self.name).write_into(target);
236 self.value_location.write_into(target);
237 self.type_id.write_into(target);
238 self.arg_index.map(|n| n.get()).write_into(target);
239 self.location.write_into(target);
240 }
241}
242
243impl Deserializable for DebugVarInfo {
244 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
245 let name: Arc<str> = String::read_from(source)?.into();
246 let value_location = DebugVarLocation::read_from(source)?;
247 let type_id = Option::<u32>::read_from(source)?;
248 let arg_index = Option::<u32>::read_from(source)?
249 .map(|n| {
250 NonZeroU32::new(n).ok_or_else(|| {
251 DeserializationError::InvalidValue("arg_index must be non-zero".into())
252 })
253 })
254 .transpose()?;
255 let location = Option::<FileLineCol>::read_from(source)?;
256
257 Ok(Self {
258 name,
259 type_id,
260 arg_index,
261 location,
262 value_location,
263 })
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use alloc::string::ToString;
270
271 use miden_debug_types::{ColumnNumber, LineNumber, Uri};
272
273 use super::*;
274 use crate::serde::{Deserializable, Serializable, SliceReader};
275
276 #[test]
277 fn debug_var_info_display_simple() {
278 let var = DebugVarInfo::new("x", DebugVarLocation::Stack(0));
279 assert_eq!(var.to_string(), "var.x = stack[0]");
280 }
281
282 #[test]
283 fn debug_var_info_display_with_arg() {
284 let mut var = DebugVarInfo::new("param", DebugVarLocation::Stack(2));
285 var.set_arg_index(1);
286 assert_eq!(var.to_string(), "var.param[arg1] = stack[2]");
287 }
288
289 #[test]
290 fn debug_var_info_display_with_location() {
291 let mut var = DebugVarInfo::new("y", DebugVarLocation::Memory(100));
292 var.set_location(FileLineCol::new(
293 Uri::new("test.rs"),
294 LineNumber::new(42).unwrap(),
295 ColumnNumber::new(5).unwrap(),
296 ));
297 assert_eq!(var.to_string(), "var.y = mem[100] [test.rs@42:5]");
298 }
299
300 #[test]
301 fn debug_var_location_display() {
302 assert_eq!(DebugVarLocation::Stack(0).to_string(), "stack[0]");
303 assert_eq!(DebugVarLocation::Memory(256).to_string(), "mem[256]");
304 assert_eq!(DebugVarLocation::Const(Felt::new(42)).to_string(), "const(42)");
305 assert_eq!(DebugVarLocation::Local(-3).to_string(), "FMP-3");
306 assert_eq!(
307 DebugVarLocation::Expression(vec![0x10, 0x20, 0x30]).to_string(),
308 "expr(10 20 30)"
309 );
310 }
311
312 #[test]
313 fn debug_var_location_serialization_round_trip() {
314 let locations = [
315 DebugVarLocation::Stack(7),
316 DebugVarLocation::Memory(0xdead_beef),
317 DebugVarLocation::Const(Felt::new(999)),
318 DebugVarLocation::Local(-3),
319 DebugVarLocation::Expression(vec![0x10, 0x20, 0x30]),
320 ];
321
322 for loc in &locations {
323 let mut bytes = Vec::new();
324 loc.write_into(&mut bytes);
325 let mut reader = SliceReader::new(&bytes);
326 let deser = DebugVarLocation::read_from(&mut reader).unwrap();
327 assert_eq!(&deser, loc);
328 }
329 }
330
331 #[test]
332 fn debug_var_info_serialization_round_trip_all_fields() {
333 let mut var = DebugVarInfo::new("full", DebugVarLocation::Expression(vec![0xaa, 0xbb]));
334 var.set_type_id(7);
335 var.set_arg_index(2);
336 var.set_location(FileLineCol::new(
337 Uri::new("lib.rs"),
338 LineNumber::new(50).unwrap(),
339 ColumnNumber::new(10).unwrap(),
340 ));
341
342 let mut bytes = Vec::new();
343 var.write_into(&mut bytes);
344 let mut reader = SliceReader::new(&bytes);
345 let deser = DebugVarInfo::read_from(&mut reader).unwrap();
346 assert_eq!(deser, var);
347 }
348}