Skip to main content

miden_core/operations/decorators/
debug_var.rs

1use 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// DEBUG VARIABLE INFO
14// ================================================================================================
15
16/// Debug information for tracking a source-level variable.
17///
18/// This decorator provides debuggers with information about where a variable's
19/// value can be found at a particular point in the program execution.
20#[derive(Clone, Debug, Eq, PartialEq)]
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22pub struct DebugVarInfo {
23    /// Variable name as it appears in source code.
24    #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_arc_str"))]
25    name: Arc<str>,
26    /// Type information (encoded as type index in debug_info section)
27    type_id: Option<u32>,
28    /// If this is a function parameter, its 1-based index.
29    arg_index: Option<NonZeroU32>,
30    /// Source file location (file:line:column).
31    /// This should only be set when the location differs from the AssemblyOp decorator
32    /// location associated with the same instruction, to avoid package bloat.
33    location: Option<FileLineCol>,
34    /// Where to find the variable's value at this point
35    value_location: DebugVarLocation,
36}
37
38impl DebugVarInfo {
39    /// Creates a new [DebugVarInfo] with the specified variable name and location.
40    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    /// Returns the variable name.
51    pub fn name(&self) -> &str {
52        &self.name
53    }
54
55    /// Returns the type ID if set.
56    pub fn type_id(&self) -> Option<u32> {
57        self.type_id
58    }
59
60    /// Sets the type ID for this variable.
61    pub fn set_type_id(&mut self, type_id: u32) {
62        self.type_id = Some(type_id);
63    }
64
65    /// Returns the argument index if this is a function parameter.
66    /// The index is 1-based.
67    pub fn arg_index(&self) -> Option<NonZeroU32> {
68        self.arg_index
69    }
70
71    /// Sets the argument index for this variable.
72    ///
73    /// # Panics
74    /// Panics if `arg_index` is 0, since argument indices are 1-based.
75    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    /// Returns the source location if set.
81    /// This is only set when the location differs from the AssemblyOp decorator location.
82    pub fn location(&self) -> Option<&FileLineCol> {
83        self.location.as_ref()
84    }
85
86    /// Sets the source location for this variable.
87    /// Only set this when the location differs from the AssemblyOp decorator location
88    /// to avoid package bloat.
89    pub fn set_location(&mut self, location: FileLineCol) {
90        self.location = Some(location);
91    }
92
93    /// Returns where the variable's value can be found.
94    pub fn value_location(&self) -> &DebugVarLocation {
95        &self.value_location
96    }
97}
98
99/// Serde deserializer for `Arc<str>`.
100#[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// DEBUG VARIABLE LOCATION
129// ================================================================================================
130
131/// Describes where a variable's value can be found during execution.
132///
133/// This enum models the different ways a variable's value might be stored
134/// during program execution, ranging from simple stack positions to complex
135/// expressions.
136#[derive(Clone, Debug, Eq, PartialEq)]
137#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
138pub enum DebugVarLocation {
139    /// Variable is at stack position N (0 = top of stack)
140    Stack(u8),
141    /// Variable is in memory at the given element address
142    Memory(u32),
143    /// Variable is a constant field element
144    Const(Felt),
145    /// Variable is in local memory at a signed offset from FMP.
146    ///
147    /// The actual memory address is computed as: `FMP + offset`
148    /// where offset is typically negative (locals are below FMP).
149    /// For example, with 3 locals: local\[0\] has offset -3, local\[2\] has offset -1.
150    Local(i16),
151    /// Complex location described by expression bytes.
152    /// This is used for variables that require computation to locate,
153    /// such as struct fields or array elements.
154    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
178// SERIALIZATION
179// ================================================================================================
180
181impl 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}