Skip to main content

miden_mast_package/debug_info/
serialization.rs

1//! Serialization and deserialization for the debug_info section.
2
3use alloc::sync::Arc;
4
5use miden_core::{
6    Word,
7    serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
8};
9use miden_debug_types::{ColumnNumber, LineNumber};
10
11use super::{
12    DEBUG_FUNCTIONS_VERSION, DEBUG_SOURCES_VERSION, DEBUG_TYPES_VERSION, DebugFieldInfo,
13    DebugFileInfo, DebugFunctionInfo, DebugFunctionsSection, DebugInlinedCallInfo,
14    DebugPrimitiveType, DebugSourcesSection, DebugTypeIdx, DebugTypeInfo, DebugTypesSection,
15    DebugVariableInfo,
16};
17
18// DEBUG TYPES SECTION SERIALIZATION
19// ================================================================================================
20
21impl Serializable for DebugTypesSection {
22    fn write_into<W: ByteWriter>(&self, target: &mut W) {
23        target.write_u8(self.version);
24
25        // Write string table
26        target.write_usize(self.strings.len());
27        for s in &self.strings {
28            write_string(target, s);
29        }
30
31        // Write type table
32        target.write_usize(self.types.len());
33        for ty in &self.types {
34            ty.write_into(target);
35        }
36    }
37}
38
39impl Deserializable for DebugTypesSection {
40    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
41        let version = source.read_u8()?;
42        if version != DEBUG_TYPES_VERSION {
43            return Err(DeserializationError::InvalidValue(alloc::format!(
44                "unsupported debug_types version: {version}, expected {DEBUG_TYPES_VERSION}"
45            )));
46        }
47
48        let strings_len = source.read_usize()?;
49        let mut strings = alloc::vec::Vec::with_capacity(strings_len);
50        for _ in 0..strings_len {
51            strings.push(read_string(source)?);
52        }
53
54        let types_len = source.read_usize()?;
55        let types = source.read_many_iter(types_len)?.collect::<Result<_, _>>()?;
56
57        Ok(Self { version, strings, types })
58    }
59}
60
61// DEBUG SOURCES SECTION SERIALIZATION
62// ================================================================================================
63
64impl Serializable for DebugSourcesSection {
65    fn write_into<W: ByteWriter>(&self, target: &mut W) {
66        target.write_u8(self.version);
67
68        // Write string table
69        target.write_usize(self.strings.len());
70        for s in &self.strings {
71            write_string(target, s);
72        }
73
74        // Write file table
75        target.write_usize(self.files.len());
76        for file in &self.files {
77            file.write_into(target);
78        }
79    }
80}
81
82impl Deserializable for DebugSourcesSection {
83    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
84        let version = source.read_u8()?;
85        if version != DEBUG_SOURCES_VERSION {
86            return Err(DeserializationError::InvalidValue(alloc::format!(
87                "unsupported debug_sources version: {version}, expected {DEBUG_SOURCES_VERSION}"
88            )));
89        }
90
91        let strings_len = source.read_usize()?;
92        let mut strings = alloc::vec::Vec::with_capacity(strings_len);
93        for _ in 0..strings_len {
94            strings.push(read_string(source)?);
95        }
96
97        let files_len = source.read_usize()?;
98        let files = source.read_many_iter(files_len)?.collect::<Result<_, _>>()?;
99
100        Ok(Self { version, strings, files })
101    }
102}
103
104// DEBUG FUNCTIONS SECTION SERIALIZATION
105// ================================================================================================
106
107impl Serializable for DebugFunctionsSection {
108    fn write_into<W: ByteWriter>(&self, target: &mut W) {
109        target.write_u8(self.version);
110
111        // Write string table
112        target.write_usize(self.strings.len());
113        for s in &self.strings {
114            write_string(target, s);
115        }
116
117        // Write function table
118        target.write_usize(self.functions.len());
119        for func in &self.functions {
120            func.write_into(target);
121        }
122    }
123}
124
125impl Deserializable for DebugFunctionsSection {
126    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
127        let version = source.read_u8()?;
128        if version != DEBUG_FUNCTIONS_VERSION {
129            return Err(DeserializationError::InvalidValue(alloc::format!(
130                "unsupported debug_functions version: {version}, expected {DEBUG_FUNCTIONS_VERSION}"
131            )));
132        }
133
134        let strings_len = source.read_usize()?;
135        let mut strings = alloc::vec::Vec::with_capacity(strings_len);
136        for _ in 0..strings_len {
137            strings.push(read_string(source)?);
138        }
139
140        let functions_len = source.read_usize()?;
141        let functions = source.read_many_iter(functions_len)?.collect::<Result<_, _>>()?;
142
143        Ok(Self { version, strings, functions })
144    }
145}
146
147// DEBUG TYPE INFO SERIALIZATION
148// ================================================================================================
149
150// Type tags for serialization
151const TYPE_TAG_PRIMITIVE: u8 = 0;
152const TYPE_TAG_POINTER: u8 = 1;
153const TYPE_TAG_ARRAY: u8 = 2;
154const TYPE_TAG_STRUCT: u8 = 3;
155const TYPE_TAG_FUNCTION: u8 = 4;
156const TYPE_TAG_UNKNOWN: u8 = 5;
157
158impl Serializable for DebugTypeInfo {
159    fn write_into<W: ByteWriter>(&self, target: &mut W) {
160        match self {
161            Self::Primitive(prim) => {
162                target.write_u8(TYPE_TAG_PRIMITIVE);
163                target.write_u8(*prim as u8);
164            },
165            Self::Pointer { pointee_type_idx } => {
166                target.write_u8(TYPE_TAG_POINTER);
167                target.write_u32(pointee_type_idx.as_u32());
168            },
169            Self::Array { element_type_idx, count } => {
170                target.write_u8(TYPE_TAG_ARRAY);
171                target.write_u32(element_type_idx.as_u32());
172                target.write_bool(count.is_some());
173                if let Some(count) = count {
174                    target.write_u32(*count);
175                }
176            },
177            Self::Struct { name_idx, size, fields } => {
178                target.write_u8(TYPE_TAG_STRUCT);
179                target.write_u32(*name_idx);
180                target.write_u32(*size);
181                target.write_usize(fields.len());
182                for field in fields {
183                    field.write_into(target);
184                }
185            },
186            Self::Function { return_type_idx, param_type_indices } => {
187                target.write_u8(TYPE_TAG_FUNCTION);
188                target.write_bool(return_type_idx.is_some());
189                if let Some(idx) = return_type_idx {
190                    target.write_u32(idx.as_u32());
191                }
192                target.write_usize(param_type_indices.len());
193                for idx in param_type_indices {
194                    target.write_u32(idx.as_u32());
195                }
196            },
197            Self::Unknown => {
198                target.write_u8(TYPE_TAG_UNKNOWN);
199            },
200        }
201    }
202}
203
204impl Deserializable for DebugTypeInfo {
205    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
206        let tag = source.read_u8()?;
207        match tag {
208            TYPE_TAG_PRIMITIVE => {
209                let prim_tag = source.read_u8()?;
210                let prim = DebugPrimitiveType::from_discriminant(prim_tag).ok_or_else(|| {
211                    DeserializationError::InvalidValue(alloc::format!(
212                        "invalid primitive type tag: {prim_tag}"
213                    ))
214                })?;
215                Ok(Self::Primitive(prim))
216            },
217            TYPE_TAG_POINTER => {
218                let pointee_type_idx = DebugTypeIdx::from(source.read_u32()?);
219                Ok(Self::Pointer { pointee_type_idx })
220            },
221            TYPE_TAG_ARRAY => {
222                let element_type_idx = DebugTypeIdx::from(source.read_u32()?);
223                let has_count = source.read_bool()?;
224                let count = if has_count { Some(source.read_u32()?) } else { None };
225                Ok(Self::Array { element_type_idx, count })
226            },
227            TYPE_TAG_STRUCT => {
228                let name_idx = source.read_u32()?;
229                let size = source.read_u32()?;
230                let fields_len = source.read_usize()?;
231                let fields = source.read_many_iter(fields_len)?.collect::<Result<_, _>>()?;
232                Ok(Self::Struct { name_idx, size, fields })
233            },
234            TYPE_TAG_FUNCTION => {
235                let has_return = source.read_bool()?;
236                let return_type_idx = if has_return {
237                    Some(DebugTypeIdx::from(source.read_u32()?))
238                } else {
239                    None
240                };
241                let params_len = source.read_usize()?;
242                let mut param_type_indices = alloc::vec::Vec::with_capacity(params_len);
243                for _ in 0..params_len {
244                    param_type_indices.push(DebugTypeIdx::from(source.read_u32()?));
245                }
246                Ok(Self::Function { return_type_idx, param_type_indices })
247            },
248            TYPE_TAG_UNKNOWN => Ok(Self::Unknown),
249            _ => Err(DeserializationError::InvalidValue(alloc::format!("invalid type tag: {tag}"))),
250        }
251    }
252}
253
254// DEBUG FIELD INFO SERIALIZATION
255// ================================================================================================
256
257impl Serializable for DebugFieldInfo {
258    fn write_into<W: ByteWriter>(&self, target: &mut W) {
259        target.write_u32(self.name_idx);
260        target.write_u32(self.type_idx.as_u32());
261        target.write_u32(self.offset);
262    }
263}
264
265impl Deserializable for DebugFieldInfo {
266    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
267        let name_idx = source.read_u32()?;
268        let type_idx = DebugTypeIdx::from(source.read_u32()?);
269        let offset = source.read_u32()?;
270        Ok(Self { name_idx, type_idx, offset })
271    }
272}
273
274// DEBUG FILE INFO SERIALIZATION
275// ================================================================================================
276
277impl Serializable for DebugFileInfo {
278    fn write_into<W: ByteWriter>(&self, target: &mut W) {
279        target.write_u32(self.path_idx);
280
281        target.write_bool(self.checksum.is_some());
282        if let Some(checksum) = &self.checksum {
283            target.write_bytes(checksum.as_ref());
284        }
285    }
286}
287
288impl Deserializable for DebugFileInfo {
289    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
290        let path_idx = source.read_u32()?;
291
292        let has_checksum = source.read_bool()?;
293        let checksum = if has_checksum {
294            let bytes = source.read_slice(32)?;
295            let mut arr = [0u8; 32];
296            arr.copy_from_slice(bytes);
297            Some(alloc::boxed::Box::new(arr))
298        } else {
299            None
300        };
301
302        Ok(Self { path_idx, checksum })
303    }
304}
305
306// DEBUG FUNCTION INFO SERIALIZATION
307// ================================================================================================
308
309impl Serializable for DebugFunctionInfo {
310    fn write_into<W: ByteWriter>(&self, target: &mut W) {
311        target.write_u32(self.name_idx);
312
313        target.write_bool(self.linkage_name_idx.is_some());
314        if let Some(idx) = self.linkage_name_idx {
315            target.write_u32(idx);
316        }
317
318        target.write_u32(self.file_idx);
319        target.write_u32(self.line.to_u32());
320        target.write_u32(self.column.to_u32());
321
322        target.write_bool(self.type_idx.is_some());
323        if let Some(idx) = self.type_idx {
324            target.write_u32(idx.as_u32());
325        }
326
327        target.write_bool(self.mast_root.is_some());
328        if let Some(root) = &self.mast_root {
329            root.write_into(target);
330        }
331
332        // Write variables
333        target.write_usize(self.variables.len());
334        for var in &self.variables {
335            var.write_into(target);
336        }
337
338        // Write inlined calls
339        target.write_usize(self.inlined_calls.len());
340        for call in &self.inlined_calls {
341            call.write_into(target);
342        }
343    }
344}
345
346impl Deserializable for DebugFunctionInfo {
347    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
348        let name_idx = source.read_u32()?;
349
350        let has_linkage_name = source.read_bool()?;
351        let linkage_name_idx = if has_linkage_name {
352            Some(source.read_u32()?)
353        } else {
354            None
355        };
356
357        let file_idx = source.read_u32()?;
358        let line_raw = source.read_u32()?;
359        let column_raw = source.read_u32()?;
360        let line = LineNumber::new(line_raw).unwrap_or_default();
361        let column = ColumnNumber::new(column_raw).unwrap_or_default();
362
363        let has_type = source.read_bool()?;
364        let type_idx = if has_type {
365            Some(DebugTypeIdx::from(source.read_u32()?))
366        } else {
367            None
368        };
369
370        let has_mast_root = source.read_bool()?;
371        let mast_root = if has_mast_root {
372            Some(Word::read_from(source)?)
373        } else {
374            None
375        };
376
377        // Read variables
378        let vars_len = source.read_usize()?;
379        let variables = source.read_many_iter(vars_len)?.collect::<Result<_, _>>()?;
380
381        // Read inlined calls
382        let calls_len = source.read_usize()?;
383        let inlined_calls = source.read_many_iter(calls_len)?.collect::<Result<_, _>>()?;
384
385        Ok(Self {
386            name_idx,
387            linkage_name_idx,
388            file_idx,
389            line,
390            column,
391            type_idx,
392            mast_root,
393            variables,
394            inlined_calls,
395        })
396    }
397}
398
399// DEBUG VARIABLE INFO SERIALIZATION
400// ================================================================================================
401
402impl Serializable for DebugVariableInfo {
403    fn write_into<W: ByteWriter>(&self, target: &mut W) {
404        target.write_u32(self.name_idx);
405        target.write_u32(self.type_idx.as_u32());
406        target.write_u32(self.arg_index);
407        target.write_u32(self.line.to_u32());
408        target.write_u32(self.column.to_u32());
409        target.write_u32(self.scope_depth);
410    }
411}
412
413impl Deserializable for DebugVariableInfo {
414    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
415        let name_idx = source.read_u32()?;
416        let type_idx = DebugTypeIdx::from(source.read_u32()?);
417        let arg_index = source.read_u32()?;
418        let line_raw = source.read_u32()?;
419        let column_raw = source.read_u32()?;
420        let line = LineNumber::new(line_raw).unwrap_or_default();
421        let column = ColumnNumber::new(column_raw).unwrap_or_default();
422        let scope_depth = source.read_u32()?;
423        Ok(Self {
424            name_idx,
425            type_idx,
426            arg_index,
427            line,
428            column,
429            scope_depth,
430        })
431    }
432}
433
434// DEBUG INLINED CALL INFO SERIALIZATION
435// ================================================================================================
436
437impl Serializable for DebugInlinedCallInfo {
438    fn write_into<W: ByteWriter>(&self, target: &mut W) {
439        target.write_u32(self.callee_idx);
440        target.write_u32(self.file_idx);
441        target.write_u32(self.line.to_u32());
442        target.write_u32(self.column.to_u32());
443    }
444}
445
446impl Deserializable for DebugInlinedCallInfo {
447    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
448        let callee_idx = source.read_u32()?;
449        let file_idx = source.read_u32()?;
450        let line_raw = source.read_u32()?;
451        let column_raw = source.read_u32()?;
452        let line = LineNumber::new(line_raw).unwrap_or_default();
453        let column = ColumnNumber::new(column_raw).unwrap_or_default();
454        Ok(Self { callee_idx, file_idx, line, column })
455    }
456}
457
458// HELPER FUNCTIONS
459// ================================================================================================
460
461fn write_string<W: ByteWriter>(target: &mut W, s: &str) {
462    let bytes = s.as_bytes();
463    target.write_usize(bytes.len());
464    target.write_bytes(bytes);
465}
466
467fn read_string<R: ByteReader>(source: &mut R) -> Result<Arc<str>, DeserializationError> {
468    let len = source.read_usize()?;
469    let bytes = source.read_slice(len)?;
470    let s = core::str::from_utf8(bytes).map_err(|err| {
471        DeserializationError::InvalidValue(alloc::format!("invalid utf-8 in string: {err}"))
472    })?;
473    Ok(Arc::from(s))
474}
475
476#[cfg(test)]
477mod tests {
478    use super::*;
479
480    fn roundtrip<T: Serializable + Deserializable + PartialEq + core::fmt::Debug>(value: &T) {
481        let mut bytes = alloc::vec::Vec::new();
482        value.write_into(&mut bytes);
483        let result = T::read_from(&mut miden_core::serde::SliceReader::new(&bytes)).unwrap();
484        assert_eq!(value, &result);
485    }
486
487    #[test]
488    fn test_debug_types_section_roundtrip() {
489        let mut section = DebugTypesSection::new();
490
491        // Add primitive types
492        let i32_type_idx = section.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I32));
493        let felt_type_idx = section.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Felt));
494
495        // Add a pointer type
496        section.add_type(DebugTypeInfo::Pointer { pointee_type_idx: i32_type_idx });
497
498        // Add an array type
499        section.add_type(DebugTypeInfo::Array {
500            element_type_idx: felt_type_idx,
501            count: Some(4),
502        });
503
504        // Add a struct type
505        let x_idx = section.add_string(Arc::from("x"));
506        let y_idx = section.add_string(Arc::from("y"));
507        let point_idx = section.add_string(Arc::from("Point"));
508        section.add_type(DebugTypeInfo::Struct {
509            name_idx: point_idx,
510            size: 16,
511            fields: alloc::vec![
512                DebugFieldInfo {
513                    name_idx: x_idx,
514                    type_idx: felt_type_idx,
515                    offset: 0,
516                },
517                DebugFieldInfo {
518                    name_idx: y_idx,
519                    type_idx: felt_type_idx,
520                    offset: 8,
521                },
522            ],
523        });
524
525        roundtrip(&section);
526    }
527
528    #[test]
529    fn test_debug_sources_section_roundtrip() {
530        let mut section = DebugSourcesSection::new();
531
532        let path_idx = section.add_string(Arc::from("test.rs"));
533        section.add_file(DebugFileInfo::new(path_idx));
534
535        let path2_idx = section.add_string(Arc::from("main.rs"));
536        section.add_file(DebugFileInfo::new(path2_idx).with_checksum([42u8; 32]));
537
538        roundtrip(&section);
539    }
540
541    #[test]
542    fn test_debug_functions_section_roundtrip() {
543        let mut section = DebugFunctionsSection::new();
544
545        let name_idx = section.add_string(Arc::from("test_function"));
546
547        let line = LineNumber::new(10).unwrap();
548        let column = ColumnNumber::new(1).unwrap();
549        let mut func = DebugFunctionInfo::new(name_idx, 0, line, column);
550        let var_name_idx = section.add_string(Arc::from("x"));
551        let var_line = LineNumber::new(10).unwrap();
552        let var_column = ColumnNumber::new(5).unwrap();
553        func.add_variable(
554            DebugVariableInfo::new(var_name_idx, DebugTypeIdx::from(0), var_line, var_column)
555                .with_arg_index(1),
556        );
557        section.add_function(func);
558
559        roundtrip(&section);
560    }
561
562    #[test]
563    fn test_empty_sections_roundtrip() {
564        roundtrip(&DebugTypesSection::new());
565        roundtrip(&DebugSourcesSection::new());
566        roundtrip(&DebugFunctionsSection::new());
567    }
568
569    #[test]
570    fn test_all_primitive_types_roundtrip() {
571        let mut section = DebugTypesSection::new();
572
573        for prim in [
574            DebugPrimitiveType::Void,
575            DebugPrimitiveType::Bool,
576            DebugPrimitiveType::I8,
577            DebugPrimitiveType::U8,
578            DebugPrimitiveType::I16,
579            DebugPrimitiveType::U16,
580            DebugPrimitiveType::I32,
581            DebugPrimitiveType::U32,
582            DebugPrimitiveType::I64,
583            DebugPrimitiveType::U64,
584            DebugPrimitiveType::I128,
585            DebugPrimitiveType::U128,
586            DebugPrimitiveType::F32,
587            DebugPrimitiveType::F64,
588            DebugPrimitiveType::Felt,
589            DebugPrimitiveType::Word,
590        ] {
591            section.add_type(DebugTypeInfo::Primitive(prim));
592        }
593
594        roundtrip(&section);
595    }
596
597    #[test]
598    fn test_function_type_roundtrip() {
599        let ty = DebugTypeInfo::Function {
600            return_type_idx: Some(DebugTypeIdx::from(0)),
601            param_type_indices: alloc::vec![
602                DebugTypeIdx::from(1),
603                DebugTypeIdx::from(2),
604                DebugTypeIdx::from(3)
605            ],
606        };
607        roundtrip(&ty);
608
609        let void_fn = DebugTypeInfo::Function {
610            return_type_idx: None,
611            param_type_indices: alloc::vec![],
612        };
613        roundtrip(&void_fn);
614    }
615
616    #[test]
617    fn test_file_info_with_checksum_roundtrip() {
618        let file = DebugFileInfo::new(0).with_checksum([42u8; 32]);
619        roundtrip(&file);
620    }
621
622    #[test]
623    fn test_function_with_mast_root_roundtrip() {
624        let line1 = LineNumber::new(1).unwrap();
625        let col1 = ColumnNumber::new(1).unwrap();
626        let mut func = DebugFunctionInfo::new(0, 0, line1, col1)
627            .with_linkage_name(1)
628            .with_type(DebugTypeIdx::from(2))
629            .with_mast_root(Word::default());
630
631        let var_line = LineNumber::new(5).unwrap();
632        let var_col = ColumnNumber::new(10).unwrap();
633        func.add_variable(
634            DebugVariableInfo::new(0, DebugTypeIdx::from(0), var_line, var_col)
635                .with_arg_index(1)
636                .with_scope_depth(2),
637        );
638
639        let call_line = LineNumber::new(20).unwrap();
640        let call_col = ColumnNumber::new(5).unwrap();
641        func.add_inlined_call(DebugInlinedCallInfo::new(0, 0, call_line, call_col));
642
643        roundtrip(&func);
644    }
645}