miden-core 0.24.0

Miden VM core components
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
use alloc::{format, sync::Arc, vec::Vec};
use core::{fmt, num::NonZeroU32};

use miden_debug_types::FileLineCol;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::{
    Felt,
    serde::{
        ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
        read_bounded_len,
    },
};

// DEBUG VARIABLE INFO
// ================================================================================================

/// Debug information for tracking a source-level variable.
///
/// This record provides debuggers with information about where a variable's
/// value can be found at a particular point in the program execution.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DebugVarInfo {
    /// Variable name as it appears in source code.
    #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_arc_str"))]
    name: Arc<str>,
    /// Type information (encoded as type index in debug_info section)
    type_id: Option<u32>,
    /// If this is a function parameter, its 1-based index.
    arg_index: Option<NonZeroU32>,
    /// Source file location (file:line:column).
    /// This should only be set when the location differs from the AssemblyOp location associated
    /// with the same instruction, to avoid package bloat.
    location: Option<FileLineCol>,
    /// Where to find the variable's value at this point
    value_location: DebugVarLocation,
}

impl DebugVarInfo {
    /// Creates a new [DebugVarInfo] with the specified variable name and location.
    pub fn new(name: impl Into<Arc<str>>, value_location: DebugVarLocation) -> Self {
        Self {
            name: name.into(),
            type_id: None,
            arg_index: None,
            location: None,
            value_location,
        }
    }

    /// Returns the variable name.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Returns the type ID if set.
    pub fn type_id(&self) -> Option<u32> {
        self.type_id
    }

    /// Sets the type ID for this variable.
    pub fn set_type_id(&mut self, type_id: u32) {
        self.type_id = Some(type_id);
    }

    /// Returns the argument index if this is a function parameter.
    /// The index is 1-based.
    pub fn arg_index(&self) -> Option<NonZeroU32> {
        self.arg_index
    }

    /// Sets the argument index for this variable.
    ///
    /// # Panics
    /// Panics if `arg_index` is 0, since argument indices are 1-based.
    pub fn set_arg_index(&mut self, arg_index: u32) {
        self.arg_index =
            Some(NonZeroU32::new(arg_index).expect("argument index must be 1-based (non-zero)"));
    }

    /// Returns the source location if set.
    /// This is only set when the location differs from the AssemblyOp location.
    pub fn location(&self) -> Option<&FileLineCol> {
        self.location.as_ref()
    }

    /// Sets the source location for this variable.
    /// Only set this when the location differs from the AssemblyOp location
    /// to avoid package bloat.
    pub fn set_location(&mut self, location: FileLineCol) {
        self.location = Some(location);
    }

    /// Returns where the variable's value can be found.
    pub fn value_location(&self) -> &DebugVarLocation {
        &self.value_location
    }

    /// Replaces the value location in-place, preserving all other fields.
    pub fn set_value_location(&mut self, value_location: DebugVarLocation) {
        self.value_location = value_location;
    }
}

/// Serde deserializer for `Arc<str>`.
#[cfg(feature = "serde")]
fn deserialize_arc_str<'de, D>(deserializer: D) -> Result<Arc<str>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    use alloc::string::String;
    let s = String::deserialize(deserializer)?;
    Ok(Arc::from(s))
}

impl fmt::Display for DebugVarInfo {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "var.{}", self.name)?;

        if let Some(arg_index) = self.arg_index {
            write!(f, "[arg{arg_index}]")?;
        }

        write!(f, " = {}", self.value_location)?;

        if let Some(loc) = &self.location {
            write!(f, " {loc}")?;
        }

        Ok(())
    }
}

// DEBUG VARIABLE LOCATION
// ================================================================================================

/// Describes where a variable's value can be found during execution.
///
/// This enum models the different ways a variable's value might be stored
/// during program execution, ranging from simple stack positions to complex
/// expressions.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum DebugVarLocation {
    /// Variable is at stack position N (0 = top of stack)
    Stack(u8),
    /// Variable is in memory at the given element address
    Memory(u32),
    /// Variable is a constant field element
    Const(Felt),
    /// Variable is in local memory at a signed offset from FMP.
    ///
    /// The actual memory address is computed as: `FMP + offset`
    /// where offset is typically negative (locals are below FMP).
    /// For example, with 3 locals: local\[0\] has offset -3, local\[2\] has offset -1.
    Local(i16),
    /// Variable is in WASM linear memory at an address computed from a global base
    /// plus a byte offset: `value_of(global[global_index]) + byte_offset`.
    ///
    /// This corresponds to DWARF's `DW_OP_fbreg` where the frame base is a WASM
    /// global (typically `__stack_pointer`). The byte offset is divided by the
    /// element size (4 for i32) to get the Miden memory element address.
    FrameBase {
        /// WASM global index whose runtime value provides the base address.
        global_index: u32,
        /// Byte offset from the base (may be positive or negative).
        byte_offset: i64,
    },
    /// Complex location described by expression bytes.
    /// This is used for variables that require computation to locate,
    /// such as struct fields or array elements.
    Expression(Vec<u8>),
}

impl fmt::Display for DebugVarLocation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Stack(pos) => write!(f, "stack[{pos}]"),
            Self::Memory(addr) => write!(f, "mem[{addr}]"),
            Self::Const(val) => write!(f, "const({})", val.as_canonical_u64()),
            Self::Local(offset) => write!(f, "FMP{offset:+}"),
            Self::FrameBase { global_index, byte_offset } => {
                write!(f, "global[{global_index}]{byte_offset:+}")
            },
            Self::Expression(bytes) => {
                write!(f, "expr(")?;
                for (i, byte) in bytes.iter().enumerate() {
                    if i > 0 {
                        write!(f, " ")?;
                    }
                    write!(f, "{byte:02x}")?;
                }
                write!(f, ")")
            },
        }
    }
}

// SERIALIZATION
// ================================================================================================

impl Serializable for DebugVarLocation {
    fn write_into<W: ByteWriter>(&self, target: &mut W) {
        match self {
            Self::Stack(pos) => {
                target.write_u8(0);
                target.write_u8(*pos);
            },
            Self::Memory(addr) => {
                target.write_u8(1);
                target.write_u32(*addr);
            },
            Self::Const(felt) => {
                target.write_u8(2);
                target.write_u64(felt.as_canonical_u64());
            },
            Self::Local(offset) => {
                target.write_u8(3);
                target.write_bytes(&offset.to_le_bytes());
            },
            Self::Expression(bytes) => {
                target.write_u8(4);
                bytes.write_into(target);
            },
            Self::FrameBase { global_index, byte_offset } => {
                target.write_u8(5);
                target.write_u32(*global_index);
                target.write_bytes(&byte_offset.to_le_bytes());
            },
        }
    }
}

impl Deserializable for DebugVarLocation {
    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
        let tag = source.read_u8()?;
        match tag {
            0 => Ok(Self::Stack(source.read_u8()?)),
            1 => Ok(Self::Memory(source.read_u32()?)),
            2 => {
                let value = source.read_u64()?;
                Ok(Self::Const(Felt::new_unchecked(value)))
            },
            3 => {
                let bytes = source.read_array::<2>()?;
                Ok(Self::Local(i16::from_le_bytes(bytes)))
            },
            4 => {
                let bytes = read_bounded_bytes(source, "debug variable expression bytes")?;
                Ok(Self::Expression(bytes))
            },
            5 => {
                let global_index = source.read_u32()?;
                let bytes = source.read_array::<8>()?;
                let byte_offset = i64::from_le_bytes(bytes);
                Ok(Self::FrameBase { global_index, byte_offset })
            },
            _ => Err(DeserializationError::InvalidValue(format!(
                "invalid DebugVarLocation tag: {tag}"
            ))),
        }
    }
}

impl Serializable for DebugVarInfo {
    fn write_into<W: ByteWriter>(&self, target: &mut W) {
        (*self.name).write_into(target);
        self.value_location.write_into(target);
        self.type_id.write_into(target);
        self.arg_index.map(core::num::NonZero::get).write_into(target);
        self.location.write_into(target);
    }
}

impl Deserializable for DebugVarInfo {
    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
        let name = read_bounded_string(source, "debug variable name bytes")?;
        let value_location = DebugVarLocation::read_from(source)?;
        let type_id = Option::<u32>::read_from(source)?;
        let arg_index = Option::<u32>::read_from(source)?
            .map(|n| {
                NonZeroU32::new(n).ok_or_else(|| {
                    DeserializationError::InvalidValue("arg_index must be non-zero".into())
                })
            })
            .transpose()?;
        let location = Option::<FileLineCol>::read_from(source)?;

        Ok(Self {
            name,
            type_id,
            arg_index,
            location,
            value_location,
        })
    }
}

fn read_bounded_string<R: ByteReader>(
    source: &mut R,
    label: &str,
) -> Result<Arc<str>, DeserializationError> {
    let len = read_bounded_len(source, label, 1)?;
    let bytes = source.read_slice(len)?;
    let value = core::str::from_utf8(bytes)
        .map_err(|err| DeserializationError::InvalidValue(format!("{err}")))?;
    Ok(Arc::from(value))
}

fn read_bounded_bytes<R: ByteReader>(
    source: &mut R,
    label: &str,
) -> Result<Vec<u8>, DeserializationError> {
    let len = read_bounded_len(source, label, 1)?;
    source.read_slice(len).map(<[u8]>::to_vec)
}

#[cfg(test)]
mod tests {
    use alloc::string::ToString;

    use miden_debug_types::{ColumnNumber, LineNumber, Uri};

    use super::*;
    use crate::serde::{Deserializable, Serializable, SliceReader};

    #[test]
    fn debug_var_info_display_simple() {
        let var = DebugVarInfo::new("x", DebugVarLocation::Stack(0));
        assert_eq!(var.to_string(), "var.x = stack[0]");
    }

    #[test]
    fn debug_var_info_rejects_oversized_name_length() {
        let bytes = [0x08, 0x2a, 0xfe, 0xfe, 0x01];
        let mut reader = SliceReader::new(&bytes);
        let err = DebugVarInfo::read_from(&mut reader).unwrap_err();
        let DeserializationError::InvalidValue(message) = err else {
            panic!("expected InvalidValue error");
        };
        assert!(message.contains("debug variable name bytes count"));
        assert!(message.contains("exceeds remaining input"));
    }

    #[test]
    fn debug_var_location_rejects_oversized_expression_length() {
        let bytes = [4, 0x08, 0x2a, 0xfe, 0xfe, 0x01];
        let mut reader = SliceReader::new(&bytes);
        let err = DebugVarLocation::read_from(&mut reader).unwrap_err();
        let DeserializationError::InvalidValue(message) = err else {
            panic!("expected InvalidValue error");
        };
        assert!(message.contains("debug variable expression bytes count"));
        assert!(message.contains("exceeds remaining input"));
    }

    #[test]
    fn debug_var_info_display_with_arg() {
        let mut var = DebugVarInfo::new("param", DebugVarLocation::Stack(2));
        var.set_arg_index(1);
        assert_eq!(var.to_string(), "var.param[arg1] = stack[2]");
    }

    #[test]
    fn debug_var_info_display_with_location() {
        let mut var = DebugVarInfo::new("y", DebugVarLocation::Memory(100));
        var.set_location(FileLineCol::new(
            Uri::new("test.rs"),
            LineNumber::new(42).unwrap(),
            ColumnNumber::new(5).unwrap(),
        ));
        assert_eq!(var.to_string(), "var.y = mem[100] [test.rs@42:5]");
    }

    #[test]
    fn debug_var_location_display() {
        assert_eq!(DebugVarLocation::Stack(0).to_string(), "stack[0]");
        assert_eq!(DebugVarLocation::Memory(256).to_string(), "mem[256]");
        assert_eq!(DebugVarLocation::Const(Felt::new_unchecked(42)).to_string(), "const(42)");
        assert_eq!(DebugVarLocation::Local(-3).to_string(), "FMP-3");
        assert_eq!(
            DebugVarLocation::FrameBase { global_index: 20, byte_offset: -12 }.to_string(),
            "global[20]-12"
        );
        assert_eq!(
            DebugVarLocation::Expression(vec![0x10, 0x20, 0x30]).to_string(),
            "expr(10 20 30)"
        );
    }

    #[test]
    fn debug_var_location_serialization_round_trip() {
        let locations = [
            DebugVarLocation::Stack(7),
            DebugVarLocation::Memory(0xdead_beef),
            DebugVarLocation::Const(Felt::new_unchecked(999)),
            DebugVarLocation::Local(-3),
            DebugVarLocation::FrameBase { global_index: 20, byte_offset: -12 },
            DebugVarLocation::Expression(vec![0x10, 0x20, 0x30]),
        ];

        for loc in &locations {
            let mut bytes = Vec::new();
            loc.write_into(&mut bytes);
            let mut reader = SliceReader::new(&bytes);
            let deser = DebugVarLocation::read_from(&mut reader).unwrap();
            assert_eq!(&deser, loc);
        }
    }

    #[test]
    fn debug_var_info_serialization_round_trip_all_fields() {
        let mut var = DebugVarInfo::new("full", DebugVarLocation::Expression(vec![0xaa, 0xbb]));
        var.set_type_id(7);
        var.set_arg_index(2);
        var.set_location(FileLineCol::new(
            Uri::new("lib.rs"),
            LineNumber::new(50).unwrap(),
            ColumnNumber::new(10).unwrap(),
        ));

        let mut bytes = Vec::new();
        var.write_into(&mut bytes);
        let mut reader = SliceReader::new(&bytes);
        let deser = DebugVarInfo::read_from(&mut reader).unwrap();
        assert_eq!(deser, var);
    }

    #[test]
    fn debug_var_info_set_value_location() {
        let mut var = DebugVarInfo::new("x", DebugVarLocation::Stack(0));
        var.set_value_location(DebugVarLocation::FrameBase { global_index: 20, byte_offset: -12 });
        assert_eq!(
            var.value_location(),
            &DebugVarLocation::FrameBase { global_index: 20, byte_offset: -12 }
        );
    }
}