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, vec::Vec};
4use core::{alloc::Layout, ptr::NonNull};
5
6use miden_assembly_syntax::ast::DebugVarLocation;
7use miden_core::{
8    Felt, Word,
9    mast::MastNodeId,
10    serde::{
11        ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
12        read_bounded_len,
13    },
14};
15use miden_debug_types::{ColumnIndex, LineIndex};
16use miden_utils_indexing::IndexVec;
17use zerocopy::{Immutable, IntoBytes, KnownLayout};
18
19use super::{
20    DEBUG_INFO_VERSION, DebugErrorMessage, DebugFieldInfo, DebugFileIdx, DebugFileInfo,
21    DebugFunctionIdx, DebugFunctionInfo, DebugLoc, DebugLocIdx, DebugPrimitiveType,
22    DebugSourceAsmOp, DebugSourceInlineCall, DebugSourceNode, DebugSourceNodeId, DebugSourceVar,
23    DebugStringIdx, DebugTypeIdx, DebugTypeInfo, DebugVariantInfo, OptionalIndex, PackageDebugInfo,
24};
25
26/// Base alignment for copied payloads. The assertions below ensure that this is sufficient for
27/// every row type decoded directly from the payload.
28const POD_BUFFER_ALIGNMENT: usize = align_of::<u64>();
29
30const _: () = {
31    assert!(align_of::<DebugFileInfo>() <= POD_BUFFER_ALIGNMENT);
32    assert!(align_of::<DebugLoc>() <= POD_BUFFER_ALIGNMENT);
33    assert!(align_of::<WireDebugFunctionInfo>() <= POD_BUFFER_ALIGNMENT);
34    assert!(align_of::<DebugSourceNodeId>() <= POD_BUFFER_ALIGNMENT);
35    assert!(align_of::<DebugErrorMessage>() <= POD_BUFFER_ALIGNMENT);
36    assert!(align_of::<DebugSourceAsmOp>() <= POD_BUFFER_ALIGNMENT);
37};
38
39/// Wire form of [`DebugFunctionInfo`]. The domain type cannot be decoded from arbitrary bytes
40/// because its field elements must be validated before constructing a [`Word`].
41#[repr(C, align(8))]
42#[derive(Clone, Copy, zerocopy::FromBytes, Immutable, IntoBytes, KnownLayout)]
43struct WireDebugFunctionInfo {
44    mast_root: [u64; 4],
45    source_node: OptionalIndex<DebugSourceNodeId>,
46    type_idx: OptionalIndex<DebugTypeIdx>,
47    linkage_name_idx: OptionalIndex<DebugStringIdx>,
48    name_idx: DebugStringIdx,
49    file_idx: DebugFileIdx,
50    line: LineIndex,
51    column: ColumnIndex,
52}
53
54// PACKAGE DEBUG INFO SERIALIZATION
55// ================================================================================================
56
57/// Fixed-size tables are padded to their row alignment and written as `zerocopy`-certified rows.
58/// Deserialization copies each payload into an aligned allocation because the padding preserves row
59/// alignment only when measured from an aligned payload base. The function table uses an explicit
60/// wire row because its field elements require validation before constructing domain values.
61#[cfg(target_endian = "little")]
62impl Serializable for PackageDebugInfo {
63    fn write_into<W: ByteWriter>(&self, target: &mut W) {
64        let mut output = Vec::<u8>::with_capacity(16 * 1024);
65
66        self.strings.write_into(&mut output);
67
68        output.write_u32(self.files().len().try_into().unwrap());
69        write_pod_slice(self.files().as_slice(), &mut output);
70
71        output.write_u32(self.locations().len().try_into().unwrap());
72        write_pod_slice(self.locations().as_slice(), &mut output);
73
74        self.types.write_into(&mut output);
75
76        output.write_u32(self.functions().len().try_into().unwrap());
77        pad_to_align::<WireDebugFunctionInfo>(&mut output);
78        write_pod_rows(
79            self.functions().iter().map(|row| {
80                let mast_root = row.mast_root.into_elements().map(|felt| felt.as_canonical_u64());
81                WireDebugFunctionInfo {
82                    mast_root,
83                    source_node: row.source_node,
84                    type_idx: row.type_idx,
85                    linkage_name_idx: row.linkage_name_idx,
86                    name_idx: row.name_idx,
87                    file_idx: row.file_idx,
88                    line: row.line,
89                    column: row.column,
90                }
91            }),
92            &mut output,
93        );
94
95        self.nodes.write_into(&mut output);
96
97        output.write_u32(self.roots().len().try_into().unwrap());
98        write_pod_slice(self.roots(), &mut output);
99
100        output.write_u32(self.error_messages().len().try_into().unwrap());
101        write_pod_slice(self.error_messages(), &mut output);
102
103        target.write_u8(self.version());
104        target.write_usize(output.len());
105        target.write_bytes(&output);
106    }
107}
108
109#[cfg(target_endian = "little")]
110impl Deserializable for PackageDebugInfo {
111    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
112        let version = source.read_u8()?;
113        if version != DEBUG_INFO_VERSION {
114            return Err(DeserializationError::InvalidValue(format!(
115                "unsupported debug_info version: {version}, expected {DEBUG_INFO_VERSION}"
116            )));
117        }
118
119        let data_len = read_bounded_len(source, "package debug info", 1)?;
120        let data = source.read_slice(data_len)?;
121        let aligned = AlignedBytes::copy_from_slice(data, POD_BUFFER_ALIGNMENT)?;
122        let mut source = PodSliceReader::new(aligned.as_slice());
123
124        let strings =
125            IndexVec::read_from_bounded_with(&mut source, "debug_info strings", 1, read_string)?;
126
127        let files_len = source.read_u32()?;
128        let files = source.read_pod_rows::<DebugFileInfo>(files_len as usize, "debug files")?;
129
130        let locations_len = source.read_u32()?;
131        let locations =
132            source.read_pod_rows::<DebugLoc>(locations_len as usize, "debug locations")?;
133
134        let types = IndexVec::read_from_bounded(&mut source, "debug_info types")?;
135
136        let functions_len = source.read_u32()?;
137        let functions = source.read_pod_rows_with::<WireDebugFunctionInfo, _, _>(
138            functions_len as usize,
139            "debug functions",
140            |row| {
141                Ok(DebugFunctionInfo {
142                    mast_root: Word::new([
143                        read_wire_felt(row.mast_root[0])?,
144                        read_wire_felt(row.mast_root[1])?,
145                        read_wire_felt(row.mast_root[2])?,
146                        read_wire_felt(row.mast_root[3])?,
147                    ]),
148                    source_node: row.source_node,
149                    type_idx: row.type_idx,
150                    linkage_name_idx: row.linkage_name_idx,
151                    name_idx: row.name_idx,
152                    file_idx: row.file_idx,
153                    line: row.line,
154                    column: row.column,
155                })
156            },
157        )?;
158
159        let nodes = IndexVec::read_from_bounded(&mut source, "debug_info nodes")?;
160
161        let roots_len = source.read_u32()? as usize;
162        let roots = source.read_pod_rows::<DebugSourceNodeId>(roots_len, "debug source roots")?;
163
164        let error_messages_len = source.read_u32()? as usize;
165        let error_messages = source
166            .read_pod_rows::<DebugErrorMessage>(error_messages_len, "debug error messages")?;
167
168        let remaining_len = source.remaining_len();
169        if remaining_len != 0 {
170            return Err(DeserializationError::InvalidValue(format!(
171                "expected {data_len} bytes to have been read, but {remaining_len} remain in the buffer"
172            )));
173        }
174
175        Ok(PackageDebugInfo {
176            version,
177            strings,
178            files: IndexVec::try_from(files).unwrap(),
179            locations: IndexVec::try_from(locations).unwrap(),
180            types,
181            functions: IndexVec::try_from(functions).unwrap(),
182            nodes,
183            roots,
184            error_messages,
185        })
186    }
187}
188
189// DEBUG SOURCE NODE SERIALIZATION
190// ================================================================================================
191
192/// Fixed-size child and assembly-op tables use the same certified row format as
193/// [`PackageDebugInfo`].
194#[cfg(target_endian = "little")]
195impl Serializable for DebugSourceNode {
196    fn write_into<W: ByteWriter>(&self, target: &mut W) {
197        let mut output = Vec::<u8>::with_capacity(
198            size_of::<DebugSourceNode>()
199                + (self.asm_ops.len() * size_of::<DebugSourceAsmOp>())
200                + (self.debug_vars.len() * size_of::<DebugSourceVar>())
201                + (self.inline_calls.len() * size_of::<DebugSourceInlineCall>()),
202        );
203
204        output.write_u32(self.exec_node.into());
205
206        output.write_u32(self.children.len().try_into().unwrap());
207        write_pod_slice(self.children.as_slice(), &mut output);
208
209        output.write_u32(self.op_start);
210        output.write_u32(self.op_end);
211
212        output.write_u32(self.asm_ops.len().try_into().unwrap());
213        write_pod_slice(self.asm_ops.as_slice(), &mut output);
214
215        self.debug_vars.write_into(&mut output);
216        self.inline_calls.write_into(&mut output);
217
218        target.write_usize(output.len());
219        target.write_bytes(&output);
220    }
221}
222
223#[cfg(target_endian = "little")]
224impl Deserializable for DebugSourceNode {
225    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
226        let data_len = read_bounded_len(source, "debug source node", 1)?;
227        let data = source.read_slice(data_len)?;
228        let aligned = AlignedBytes::copy_from_slice(data, POD_BUFFER_ALIGNMENT)?;
229        let mut source = PodSliceReader::new(aligned.as_slice());
230
231        let exec_node = MastNodeId::new_unchecked(source.read_u32()?);
232
233        let children_len = source.read_u32()? as usize;
234        let children =
235            source.read_pod_rows::<DebugSourceNodeId>(children_len, "debug source children")?;
236
237        let op_start = source.read_u32()?;
238        let op_end = source.read_u32()?;
239
240        let asm_ops_len = source.read_u32()? as usize;
241        let asm_ops =
242            source.read_pod_rows::<DebugSourceAsmOp>(asm_ops_len, "debug assembly operations")?;
243
244        let debug_vars = Vec::read_from(&mut source)?;
245        let inline_calls = Vec::read_from(&mut source)?;
246
247        let remaining_len = source.remaining_len();
248        if remaining_len != 0 {
249            return Err(DeserializationError::InvalidValue(format!(
250                "expected {data_len} bytes to have been read, but {remaining_len} remain in the buffer"
251            )));
252        }
253
254        Ok(Self {
255            exec_node,
256            children,
257            op_start,
258            op_end,
259            asm_ops,
260            debug_vars,
261            inline_calls,
262        })
263    }
264
265    fn min_serialized_size() -> usize {
266        1 + DebugSourceNodeId::min_serialized_size()
267            + Vec::<DebugSourceNodeId>::min_serialized_size()
268            + 8
269            + 1
270            + Vec::<DebugSourceVar>::min_serialized_size()
271            + Vec::<DebugSourceInlineCall>::min_serialized_size()
272    }
273}
274
275// DEBUG SOURCE VARIABLE SERIALIZATION
276// ================================================================================================
277
278impl Serializable for DebugSourceVar {
279    fn write_into<W: ByteWriter>(&self, target: &mut W) {
280        target.write_u32(self.op_idx);
281        self.name_idx.write_into(target);
282        self.type_id.write_into(target);
283        target.write_u32(self.arg_idx.map(core::num::NonZeroU32::get).unwrap_or_default());
284        self.location_idx.write_into(target);
285        self.value_location.write_into(target);
286    }
287}
288
289impl Deserializable for DebugSourceVar {
290    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
291        let op_idx = source.read_u32()?;
292        let name_idx = DebugStringIdx::read_from(source)?;
293        let type_id = Option::<DebugTypeIdx>::read_from(source)?;
294        let arg_idx = core::num::NonZeroU32::new(source.read_u32()?);
295        let location_idx = Option::<DebugLocIdx>::read_from(source)?;
296        let value_location = DebugVarLocation::read_from(source)?;
297        Ok(Self {
298            op_idx,
299            name_idx,
300            type_id,
301            arg_idx,
302            location_idx,
303            value_location,
304        })
305    }
306
307    fn min_serialized_size() -> usize {
308        4 + DebugStringIdx::min_serialized_size()
309            + 1
310            + 4
311            + 1
312            + DebugVarLocation::min_serialized_size()
313    }
314}
315
316// DEBUG INLINE CALL SERIALIZATION
317// ================================================================================================
318
319impl Serializable for DebugSourceInlineCall {
320    fn write_into<W: ByteWriter>(&self, target: &mut W) {
321        target.write_u32(self.op_idx);
322        self.callee_idx.write_into(target);
323        self.loc_idx.write_into(target);
324    }
325}
326
327impl Deserializable for DebugSourceInlineCall {
328    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
329        let op_idx = source.read_u32()?;
330        let callee_idx = DebugFunctionIdx::read_from(source)?;
331        let loc_idx = DebugLocIdx::read_from(source)?;
332        Ok(DebugSourceInlineCall { op_idx, callee_idx, loc_idx })
333    }
334
335    fn min_serialized_size() -> usize {
336        4 + DebugFunctionIdx::min_serialized_size() + DebugLocIdx::min_serialized_size()
337    }
338}
339
340// DEBUG TYPE INFO SERIALIZATION
341// ================================================================================================
342
343// Type tags for serialization
344const TYPE_TAG_PRIMITIVE: u8 = 0;
345const TYPE_TAG_POINTER: u8 = 1;
346const TYPE_TAG_ARRAY: u8 = 2;
347const TYPE_TAG_STRUCT: u8 = 3;
348const TYPE_TAG_FUNCTION: u8 = 4;
349const TYPE_TAG_UNKNOWN: u8 = 5;
350const TYPE_TAG_ENUM: u8 = 6;
351
352impl Serializable for DebugTypeInfo {
353    fn write_into<W: ByteWriter>(&self, target: &mut W) {
354        match self {
355            Self::Primitive(prim) => {
356                target.write_u8(TYPE_TAG_PRIMITIVE);
357                target.write_u8(*prim as u8);
358            },
359            Self::Pointer { pointee_type_idx } => {
360                target.write_u8(TYPE_TAG_POINTER);
361                pointee_type_idx.write_into(target);
362            },
363            Self::Array { element_type_idx, count } => {
364                target.write_u8(TYPE_TAG_ARRAY);
365                element_type_idx.write_into(target);
366                target.write_bool(count.is_some());
367                if let Some(count) = count {
368                    target.write_u32(*count);
369                }
370            },
371            Self::Struct { name_idx, size, fields } => {
372                target.write_u8(TYPE_TAG_STRUCT);
373                name_idx.write_into(target);
374                target.write_u32(*size);
375                target.write_usize(fields.len());
376                for field in fields {
377                    field.write_into(target);
378                }
379            },
380            Self::Function { return_type_idx, param_type_indices } => {
381                target.write_u8(TYPE_TAG_FUNCTION);
382                target.write_bool(return_type_idx.is_some());
383                if let Some(idx) = return_type_idx {
384                    idx.write_into(target);
385                }
386                target.write_usize(param_type_indices.len());
387                for idx in param_type_indices {
388                    idx.write_into(target);
389                }
390            },
391            Self::Enum {
392                name_idx,
393                size,
394                discriminant_type_idx,
395                variants,
396            } => {
397                target.write_u8(TYPE_TAG_ENUM);
398                name_idx.write_into(target);
399                target.write_u32(*size);
400                discriminant_type_idx.write_into(target);
401                target.write_usize(variants.len());
402                for variant in variants {
403                    variant.write_into(target);
404                }
405            },
406            Self::Unknown => {
407                target.write_u8(TYPE_TAG_UNKNOWN);
408            },
409        }
410    }
411}
412
413impl Deserializable for DebugTypeInfo {
414    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
415        let tag = source.read_u8()?;
416        match tag {
417            TYPE_TAG_PRIMITIVE => {
418                let prim_tag = source.read_u8()?;
419                let prim = DebugPrimitiveType::from_discriminant(prim_tag).ok_or_else(|| {
420                    DeserializationError::InvalidValue(alloc::format!(
421                        "invalid primitive type tag: {prim_tag}"
422                    ))
423                })?;
424                Ok(Self::Primitive(prim))
425            },
426            TYPE_TAG_POINTER => {
427                let pointee_type_idx = DebugTypeIdx::from(source.read_u32()?);
428                Ok(Self::Pointer { pointee_type_idx })
429            },
430            TYPE_TAG_ARRAY => {
431                let element_type_idx = DebugTypeIdx::from(source.read_u32()?);
432                let has_count = source.read_bool()?;
433                let count = if has_count { Some(source.read_u32()?) } else { None };
434                Ok(Self::Array { element_type_idx, count })
435            },
436            TYPE_TAG_STRUCT => {
437                let name_idx = DebugStringIdx::read_from(source)?;
438                let size = source.read_u32()?;
439                let fields_len = read_bounded_len(source, "debug struct fields", 1)?;
440                let fields = source.read_many_iter(fields_len)?.collect::<Result<_, _>>()?;
441                Ok(Self::Struct { name_idx, size, fields })
442            },
443            TYPE_TAG_FUNCTION => {
444                let has_return = source.read_bool()?;
445                let return_type_idx = if has_return {
446                    Some(DebugTypeIdx::from(source.read_u32()?))
447                } else {
448                    None
449                };
450                let param_type_indices =
451                    read_debug_type_indices(source, "debug function parameters")?;
452                Ok(Self::Function { return_type_idx, param_type_indices })
453            },
454            TYPE_TAG_ENUM => {
455                let name_idx = DebugStringIdx::read_from(source)?;
456                let size = source.read_u32()?;
457                let discriminant_type_idx = DebugTypeIdx::from(source.read_u32()?);
458                let variants_len = read_bounded_len(source, "debug enum variants", 1)?;
459                let variants = source.read_many_iter(variants_len)?.collect::<Result<_, _>>()?;
460                Ok(Self::Enum {
461                    name_idx,
462                    size,
463                    discriminant_type_idx,
464                    variants,
465                })
466            },
467            TYPE_TAG_UNKNOWN => Ok(Self::Unknown),
468            _ => Err(DeserializationError::InvalidValue(alloc::format!("invalid type tag: {tag}"))),
469        }
470    }
471
472    fn min_serialized_size() -> usize {
473        // The unknown type consists solely of its tag. All other variants are larger.
474        1
475    }
476}
477
478// DEBUG FIELD INFO SERIALIZATION
479// ================================================================================================
480
481impl Serializable for DebugFieldInfo {
482    fn write_into<W: ByteWriter>(&self, target: &mut W) {
483        self.name_idx.write_into(target);
484        self.type_idx.write_into(target);
485        target.write_u32(self.offset);
486    }
487}
488
489impl Deserializable for DebugFieldInfo {
490    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
491        let name_idx = DebugStringIdx::read_from(source)?;
492        let type_idx = DebugTypeIdx::from(source.read_u32()?);
493        let offset = source.read_u32()?;
494        Ok(Self { name_idx, type_idx, offset })
495    }
496}
497
498// DEBUG VARIANT INFO SERIALIZATION
499// ================================================================================================
500
501impl Serializable for DebugVariantInfo {
502    fn write_into<W: ByteWriter>(&self, target: &mut W) {
503        self.name_idx.write_into(target);
504        target.write_bool(self.type_idx.is_some());
505        if let Some(type_idx) = self.type_idx {
506            type_idx.write_into(target);
507        }
508        target.write_bool(self.payload_offset.is_some());
509        if let Some(payload_offset) = self.payload_offset {
510            target.write_u32(payload_offset);
511        }
512        target.write_u64((self.discriminant >> 64) as u64);
513        target.write_u64(self.discriminant as u64);
514    }
515}
516
517impl Deserializable for DebugVariantInfo {
518    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
519        let name_idx = DebugStringIdx::read_from(source)?;
520        let type_idx = if source.read_bool()? {
521            Some(DebugTypeIdx::from(source.read_u32()?))
522        } else {
523            None
524        };
525        let payload_offset = if source.read_bool()? {
526            Some(source.read_u32()?)
527        } else {
528            None
529        };
530        let hi = source.read_u64()? as u128;
531        let lo = source.read_u64()? as u128;
532        Ok(Self {
533            name_idx,
534            type_idx,
535            payload_offset,
536            discriminant: (hi << 64) | lo,
537        })
538    }
539
540    fn min_serialized_size() -> usize {
541        // The minimum encoding has no payload type or offset: one string-table index, two
542        // one-byte option discriminants, and the two halves of the discriminant value.
543        DebugStringIdx::min_serialized_size()
544            + 2 * u8::min_serialized_size()
545            + 2 * u64::min_serialized_size()
546    }
547}
548
549// DEBUG FILE INFO SERIALIZATION
550// ================================================================================================
551
552impl Serializable for DebugFileInfo {
553    fn write_into<W: ByteWriter>(&self, target: &mut W) {
554        self.path_idx.write_into(target);
555        self.checksum.write_into(target);
556    }
557}
558
559impl Deserializable for DebugFileInfo {
560    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
561        let path_idx = DebugStringIdx::read_from(source)?;
562
563        let bytes = source.read_slice(32)?;
564        let mut checksum = [0u8; 32];
565        checksum.copy_from_slice(bytes);
566
567        Ok(Self { path_idx, checksum })
568    }
569
570    fn min_serialized_size() -> usize {
571        DebugStringIdx::min_serialized_size() + size_of::<[u8; 32]>()
572    }
573}
574
575// HELPER FUNCTIONS
576// ================================================================================================
577
578/// Owns a byte allocation with the alignment required by the certified POD row types.
579struct AlignedBytes {
580    ptr: Option<NonNull<u8>>,
581    layout: Layout,
582}
583
584impl AlignedBytes {
585    fn copy_from_slice(source: &[u8], alignment: usize) -> Result<Self, DeserializationError> {
586        let layout = Layout::from_size_align(source.len(), alignment).map_err(|_| {
587            DeserializationError::InvalidValue(format!(
588                "debug info payload size {} is too large",
589                source.len()
590            ))
591        })?;
592        if source.is_empty() {
593            return Ok(Self { ptr: None, layout });
594        }
595
596        // SAFETY: `layout` is non-zero and valid in this branch.
597        let ptr = unsafe { alloc::alloc::alloc(layout) };
598        let Some(ptr) = NonNull::new(ptr) else {
599            alloc::alloc::handle_alloc_error(layout)
600        };
601        // SAFETY: `ptr` owns `source.len()` writable bytes and does not overlap `source`.
602        unsafe {
603            ptr.as_ptr().copy_from_nonoverlapping(source.as_ptr(), source.len());
604        }
605        Ok(Self { ptr: Some(ptr), layout })
606    }
607
608    fn as_slice(&self) -> &[u8] {
609        let Some(ptr) = self.ptr else {
610            return &[];
611        };
612        // SAFETY: `ptr` was allocated with `self.layout`, every byte was initialized by the copy in
613        // `copy_from_slice`, and the allocation remains owned by `self` for the returned borrow.
614        unsafe { core::slice::from_raw_parts(ptr.as_ptr(), self.layout.size()) }
615    }
616}
617
618impl Drop for AlignedBytes {
619    fn drop(&mut self) {
620        if let Some(ptr) = self.ptr {
621            // SAFETY: `ptr` was allocated with this exact layout and has not been freed.
622            unsafe {
623                alloc::alloc::dealloc(ptr.as_ptr(), self.layout);
624            }
625        }
626    }
627}
628
629struct PodSliceReader<'a> {
630    source: &'a [u8],
631    pos: usize,
632}
633
634impl<'a> PodSliceReader<'a> {
635    fn new(source: &'a [u8]) -> Self {
636        Self { source, pos: 0 }
637    }
638
639    fn remaining_len(&self) -> usize {
640        self.source.len() - self.pos
641    }
642
643    fn read_pod_rows<T>(&mut self, len: usize, label: &str) -> Result<Vec<T>, DeserializationError>
644    where
645        T: Copy + zerocopy::FromBytes + Immutable + KnownLayout,
646    {
647        self.skip_alignment_padding::<T>()?;
648        let byte_len = len.checked_mul(size_of::<T>()).ok_or_else(|| {
649            DeserializationError::InvalidValue(alloc::format!(
650                "{label} row count {len} overflows row size {}",
651                size_of::<T>()
652            ))
653        })?;
654        let bytes = self.read_slice(byte_len)?;
655        let rows: &[T] = <[T] as zerocopy::FromBytes>::ref_from_bytes(bytes).map_err(|_| {
656            DeserializationError::InvalidValue(alloc::format!(
657                "{label} bytes do not form aligned POD rows"
658            ))
659        })?;
660        Ok(rows.to_vec())
661    }
662
663    fn read_pod_rows_with<T, U, F>(
664        &mut self,
665        len: usize,
666        label: &str,
667        map: F,
668    ) -> Result<Vec<U>, DeserializationError>
669    where
670        T: Copy + zerocopy::FromBytes + Immutable + KnownLayout,
671        F: FnMut(T) -> Result<U, DeserializationError>,
672    {
673        self.skip_alignment_padding::<T>()?;
674        let byte_len = len.checked_mul(size_of::<T>()).ok_or_else(|| {
675            DeserializationError::InvalidValue(alloc::format!(
676                "{label} row count {len} overflows row size {}",
677                size_of::<T>()
678            ))
679        })?;
680        let bytes = self.read_slice(byte_len)?;
681        let rows: &[T] = <[T] as zerocopy::FromBytes>::ref_from_bytes(bytes).map_err(|_| {
682            DeserializationError::InvalidValue(alloc::format!(
683                "{label} bytes do not form aligned POD rows"
684            ))
685        })?;
686        rows.iter().copied().map(map).collect()
687    }
688
689    fn skip_alignment_padding<T>(&mut self) -> Result<(), DeserializationError> {
690        let padding_required = self.pos.next_multiple_of(align_of::<T>()) - self.pos;
691        self.pos += padding_required;
692        if self.pos > self.source.len() {
693            Err(DeserializationError::UnexpectedEOF)
694        } else {
695            Ok(())
696        }
697    }
698}
699
700impl ByteReader for PodSliceReader<'_> {
701    fn max_alloc(&self, element_size: usize) -> usize {
702        self.remaining_len().checked_div(element_size).unwrap_or(usize::MAX)
703    }
704
705    fn read_u8(&mut self) -> Result<u8, DeserializationError> {
706        self.check_eor(1)?;
707        let result = self.source[self.pos];
708        self.pos += 1;
709        Ok(result)
710    }
711
712    fn peek_u8(&self) -> Result<u8, DeserializationError> {
713        self.check_eor(1)?;
714        Ok(self.source[self.pos])
715    }
716
717    fn read_slice(&mut self, len: usize) -> Result<&[u8], DeserializationError> {
718        self.check_eor(len)?;
719        let result = &self.source[self.pos..self.pos + len];
720        self.pos += len;
721        Ok(result)
722    }
723
724    fn read_array<const N: usize>(&mut self) -> Result<[u8; N], DeserializationError> {
725        self.check_eor(N)?;
726        let mut result = [0_u8; N];
727        result.copy_from_slice(&self.source[self.pos..self.pos + N]);
728        self.pos += N;
729        Ok(result)
730    }
731
732    fn check_eor(&self, num_bytes: usize) -> Result<(), DeserializationError> {
733        self.pos
734            .checked_add(num_bytes)
735            .filter(|end| *end <= self.source.len())
736            .map(|_| ())
737            .ok_or(DeserializationError::UnexpectedEOF)
738    }
739
740    fn has_more_bytes(&self) -> bool {
741        self.remaining_len() != 0
742    }
743}
744
745fn pad_to_align<T>(output: &mut Vec<u8>) {
746    let padding_required = output.len().next_multiple_of(align_of::<T>()) - output.len();
747    output.resize(output.len() + padding_required, 0);
748}
749
750fn write_pod_slice<T: IntoBytes + Immutable>(slice: &[T], target: &mut Vec<u8>) {
751    pad_to_align::<T>(target);
752    target.write_bytes(slice.as_bytes());
753}
754
755fn write_pod_rows<T, I>(rows: I, target: &mut Vec<u8>)
756where
757    T: IntoBytes + Immutable,
758    I: IntoIterator<Item = T>,
759{
760    let rows: Vec<T> = rows.into_iter().collect();
761    target.write_bytes(rows.as_bytes());
762}
763
764fn read_wire_felt(value: u64) -> Result<Felt, DeserializationError> {
765    Felt::new(value).map_err(|err| {
766        DeserializationError::InvalidValue(alloc::format!(
767            "invalid field element in debug function MAST root: {err}"
768        ))
769    })
770}
771
772fn read_string<R: ByteReader>(source: &mut R) -> Result<Arc<str>, DeserializationError> {
773    let len = read_bounded_len(source, "debug string bytes", 1)?;
774    let bytes = source.read_slice(len)?;
775    let s = core::str::from_utf8(bytes).map_err(|err| {
776        DeserializationError::InvalidValue(alloc::format!("invalid utf-8 in string: {err}"))
777    })?;
778    Ok(Arc::from(s))
779}
780
781fn read_debug_type_indices<R: ByteReader>(
782    source: &mut R,
783    label: &str,
784) -> Result<Vec<DebugTypeIdx>, DeserializationError> {
785    let len = read_bounded_len(source, label, DebugTypeIdx::min_serialized_size())?;
786    source.read_many_iter(len)?.collect::<Result<_, _>>()
787}
788
789#[cfg(test)]
790mod tests {
791    use core::cell::Cell;
792
793    use miden_assembly_syntax::ast::DebugVarLocation;
794    use miden_core::{Felt, Word};
795    use miden_debug_types::{ByteIndex, ColumnNumber, LineNumber, Location, Uri};
796
797    use super::*;
798    use crate::debug_info::{DebugFileIdx, PackageDebugInfoBuilder};
799
800    struct FixedBudgetReader<'a> {
801        inner: miden_core::serde::SliceReader<'a>,
802        max_bytes: usize,
803        largest_requested_element_size: Cell<usize>,
804    }
805
806    impl<'a> FixedBudgetReader<'a> {
807        fn new(bytes: &'a [u8], max_bytes: usize) -> Self {
808            Self {
809                inner: miden_core::serde::SliceReader::new(bytes),
810                max_bytes,
811                largest_requested_element_size: Cell::new(0),
812            }
813        }
814    }
815
816    impl<'a> ByteReader for FixedBudgetReader<'a> {
817        fn read_u8(&mut self) -> Result<u8, DeserializationError> {
818            self.inner.read_u8()
819        }
820
821        fn peek_u8(&self) -> Result<u8, DeserializationError> {
822            self.inner.peek_u8()
823        }
824
825        fn read_slice(&mut self, len: usize) -> Result<&[u8], DeserializationError> {
826            self.inner.read_slice(len)
827        }
828
829        fn read_array<const N: usize>(&mut self) -> Result<[u8; N], DeserializationError> {
830            self.inner.read_array()
831        }
832
833        fn check_eor(&self, num_bytes: usize) -> Result<(), DeserializationError> {
834            self.inner.check_eor(num_bytes)
835        }
836
837        fn has_more_bytes(&self) -> bool {
838            self.inner.has_more_bytes()
839        }
840
841        fn max_alloc(&self, element_size: usize) -> usize {
842            self.largest_requested_element_size
843                .set(self.largest_requested_element_size.get().max(element_size));
844            if element_size == 0 {
845                usize::MAX
846            } else {
847                self.max_bytes.checked_div(element_size).unwrap_or(0)
848            }
849        }
850    }
851
852    fn function_type_bytes(params_len: usize) -> Vec<u8> {
853        let mut bytes = Vec::new();
854        bytes.write_u8(TYPE_TAG_FUNCTION);
855        bytes.write_bool(false);
856        bytes.write_usize(params_len);
857        for _ in 0..params_len {
858            bytes.write_u32(0);
859        }
860        bytes
861    }
862
863    fn roundtrip<T: Serializable + Deserializable + PartialEq + core::fmt::Debug>(value: &T) {
864        let mut bytes = Vec::new();
865        value.write_into(&mut bytes);
866        let result = T::read_from(&mut miden_core::serde::SliceReader::new(&bytes)).unwrap();
867        assert_eq!(value, &result);
868    }
869
870    #[test]
871    fn pod_row_reader_rejects_byte_length_overflow() {
872        let mut reader = PodSliceReader::new(&[]);
873        let result = reader.read_pod_rows::<DebugErrorMessage>(usize::MAX, "test error messages");
874        let error = result.unwrap_err();
875
876        let DeserializationError::InvalidValue(message) = error else {
877            panic!("expected InvalidValue error");
878        };
879        assert!(message.contains("overflows row size"));
880    }
881
882    #[test]
883    fn pod_row_reader_decodes_certified_rows() {
884        let expected = DebugErrorMessage::new(42, DebugStringIdx::from(7));
885        let mut aligned = [0_u64; 2];
886        aligned.as_mut_bytes()[..size_of::<DebugErrorMessage>()]
887            .copy_from_slice(expected.as_bytes());
888        let mut reader = PodSliceReader::new(aligned.as_bytes());
889        let decoded = reader.read_pod_rows::<DebugErrorMessage>(1, "test error messages").unwrap();
890
891        assert_eq!(decoded, [expected]);
892    }
893
894    #[test]
895    fn debug_variant_min_serialized_size_is_accepted_by_slice_reader() {
896        let variant = DebugVariantInfo {
897            name_idx: DebugStringIdx::from(0),
898            type_idx: None,
899            payload_offset: None,
900            discriminant: 0,
901        };
902        let mut bytes = Vec::new();
903        variant.write_into(&mut bytes);
904
905        assert_eq!(bytes.len(), DebugVariantInfo::min_serialized_size());
906
907        let mut reader = miden_core::serde::SliceReader::new(&bytes);
908        let mut variants = reader.read_many_iter::<DebugVariantInfo>(1).unwrap();
909        assert_eq!(variants.next().unwrap().unwrap(), variant);
910        assert!(variants.next().is_none());
911    }
912
913    #[test]
914    fn debug_type_initial_capacity_is_bounded_by_in_memory_size() {
915        const PAYLOAD_BYTES: usize = 256;
916
917        let mut bytes = Vec::new();
918        bytes.write_usize(PAYLOAD_BYTES);
919        bytes.resize(bytes.len() + PAYLOAD_BYTES, u8::MAX);
920        let mut reader = FixedBudgetReader::new(&bytes, PAYLOAD_BYTES);
921
922        let result =
923            IndexVec::<DebugTypeIdx, DebugTypeInfo>::read_from_bounded(&mut reader, "debug types");
924
925        assert!(result.is_err(), "the first invalid type tag should stop decoding");
926        assert_eq!(
927            reader.largest_requested_element_size.get(),
928            size_of::<DebugTypeInfo>(),
929            "the speculative capacity must be bounded using the in-memory row size",
930        );
931    }
932
933    #[test]
934    fn package_debug_info_trailing_data_error_reports_remaining_bytes() {
935        const TRAILING: &[u8] = &[0xaa, 0xbb, 0xcc];
936
937        let serialized = PackageDebugInfo::default().to_bytes();
938        let mut reader = miden_core::serde::SliceReader::new(&serialized);
939        let version = reader.read_u8().unwrap();
940        let data_len = reader.read_usize().unwrap();
941        let data = reader.read_slice(data_len).unwrap().to_vec();
942        assert!(!reader.has_more_bytes());
943
944        let mut malformed = Vec::new();
945        malformed.write_u8(version);
946        malformed.write_usize(data_len + TRAILING.len());
947        malformed.write_bytes(&data);
948        malformed.write_bytes(TRAILING);
949
950        let error =
951            PackageDebugInfo::read_from(&mut miden_core::serde::SliceReader::new(&malformed))
952                .unwrap_err();
953        let DeserializationError::InvalidValue(message) = error else {
954            panic!("expected InvalidValue error");
955        };
956        assert!(message.contains("but 3 remain in the buffer"), "{message}");
957    }
958
959    #[test]
960    fn debug_source_node_trailing_data_error_reports_remaining_bytes() {
961        const TRAILING: &[u8] = &[0xaa, 0xbb];
962
963        let source_node = DebugSourceNode {
964            exec_node: MastNodeId::new_unchecked(0),
965            children: Vec::new(),
966            op_start: 0,
967            op_end: 0,
968            asm_ops: Vec::new(),
969            debug_vars: Vec::new(),
970            inline_calls: Vec::new(),
971        };
972        let serialized = source_node.to_bytes();
973        let mut reader = miden_core::serde::SliceReader::new(&serialized);
974        let data_len = reader.read_usize().unwrap();
975        let data = reader.read_slice(data_len).unwrap().to_vec();
976        assert!(!reader.has_more_bytes());
977
978        let mut malformed = Vec::new();
979        malformed.write_usize(data_len + TRAILING.len());
980        malformed.write_bytes(&data);
981        malformed.write_bytes(TRAILING);
982
983        let error =
984            DebugSourceNode::read_from(&mut miden_core::serde::SliceReader::new(&malformed))
985                .unwrap_err();
986        let DeserializationError::InvalidValue(message) = error else {
987            panic!("expected InvalidValue error");
988        };
989        assert!(message.contains("but 2 remain in the buffer"), "{message}");
990    }
991
992    fn roundtrip_debug_info(value: &PackageDebugInfo) -> PackageDebugInfo {
993        let bytes = value.to_bytes();
994        let result =
995            PackageDebugInfo::read_from(&mut miden_core::serde::SliceReader::new(bytes.as_slice()))
996                .unwrap();
997        assert_eq!(result.version(), value.version());
998        assert_eq!(result.strings(), value.strings());
999        assert_eq!(result.files(), value.files());
1000        assert_eq!(result.locations(), value.locations());
1001        assert_eq!(result.types(), value.types());
1002        assert_eq!(result.functions(), value.functions());
1003        assert_eq!(result.nodes().as_slice(), value.nodes().as_slice());
1004        assert_eq!(result.roots(), value.roots());
1005        assert_eq!(result.error_messages(), value.error_messages());
1006        result
1007    }
1008
1009    #[test]
1010    fn test_debug_types_roundtrip() {
1011        let mut builder = PackageDebugInfoBuilder::default();
1012
1013        let i32_type_idx = builder.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I32));
1014        let felt_type_idx = builder.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Felt));
1015        builder.add_type(DebugTypeInfo::Pointer { pointee_type_idx: i32_type_idx });
1016        builder.add_type(DebugTypeInfo::Array {
1017            element_type_idx: felt_type_idx,
1018            count: Some(4),
1019        });
1020
1021        let x_idx = builder.add_string("x");
1022        let y_idx = builder.add_string("y");
1023        let point_idx = builder.add_string("Point");
1024        builder.add_type(DebugTypeInfo::Struct {
1025            name_idx: point_idx,
1026            size: 16,
1027            fields: alloc::vec![
1028                DebugFieldInfo {
1029                    name_idx: x_idx,
1030                    type_idx: felt_type_idx,
1031                    offset: 0,
1032                },
1033                DebugFieldInfo {
1034                    name_idx: y_idx,
1035                    type_idx: felt_type_idx,
1036                    offset: 8,
1037                },
1038            ],
1039        });
1040
1041        let status_idx = builder.add_string("Status");
1042        let ok_idx = builder.add_string("Ok");
1043        let err_idx = builder.add_string("Err");
1044        builder.add_type(DebugTypeInfo::Enum {
1045            name_idx: status_idx,
1046            size: 8,
1047            discriminant_type_idx: i32_type_idx,
1048            variants: alloc::vec![
1049                DebugVariantInfo {
1050                    name_idx: ok_idx,
1051                    type_idx: None,
1052                    payload_offset: None,
1053                    discriminant: 0,
1054                },
1055                DebugVariantInfo {
1056                    name_idx: err_idx,
1057                    type_idx: Some(felt_type_idx),
1058                    payload_offset: Some(8),
1059                    discriminant: 1,
1060                },
1061            ],
1062        });
1063
1064        let debug_info = *builder.build();
1065        let result = roundtrip_debug_info(&debug_info);
1066        assert_eq!(result.strings(), debug_info.strings());
1067        assert_eq!(result.types(), debug_info.types());
1068    }
1069
1070    #[test]
1071    fn test_debug_sources_roundtrip() {
1072        let mut builder = PackageDebugInfoBuilder::default();
1073        builder.add_file(Uri::new("test.rs"), None);
1074        builder.add_file(Uri::new("main.rs"), Some([42u8; 32]));
1075
1076        let debug_info = *builder.build();
1077        let result = roundtrip_debug_info(&debug_info);
1078        assert_eq!(result.strings(), debug_info.strings());
1079        assert_eq!(result.files(), debug_info.files());
1080        assert_eq!(result.files()[DebugFileIdx::from(1)].checksum(), Some(&[42u8; 32]));
1081    }
1082
1083    #[test]
1084    fn test_debug_functions_roundtrip() {
1085        let mut builder = PackageDebugInfoBuilder::default();
1086        let name_idx = builder.add_string("test_function");
1087        let file_idx = builder.add_file(Uri::new("test.masm"), None);
1088        let line = LineNumber::new(10).unwrap();
1089        let column = ColumnNumber::new(1).unwrap();
1090        builder.add_function(DebugFunctionInfo::new(
1091            None,
1092            name_idx,
1093            file_idx,
1094            line,
1095            column,
1096            Word::default(),
1097        ));
1098
1099        let debug_info = *builder.build();
1100        let result = roundtrip_debug_info(&debug_info);
1101        assert_eq!(result.functions(), debug_info.functions());
1102    }
1103
1104    #[test]
1105    fn debug_function_v2_wire_bytes_are_stable() {
1106        const EXPECTED_ROW: [u8; size_of::<WireDebugFunctionInfo>()] = [
1107            1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0,
1108            0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0, 11, 0, 0, 0, 13,
1109            0, 0, 0, 15, 0, 0, 0, 17, 0, 0, 0, 19, 0, 0, 0,
1110        ];
1111
1112        let function = DebugFunctionInfo {
1113            mast_root: Word::new([
1114                Felt::new(1).unwrap(),
1115                Felt::new(2).unwrap(),
1116                Felt::new(3).unwrap(),
1117                Felt::new(4).unwrap(),
1118            ]),
1119            source_node: Some(DebugSourceNodeId::from(7)).into(),
1120            type_idx: Some(DebugTypeIdx::from(9)).into(),
1121            linkage_name_idx: Some(DebugStringIdx::from(11)).into(),
1122            name_idx: DebugStringIdx::from(13),
1123            file_idx: DebugFileIdx::from(15),
1124            line: LineIndex::from(17),
1125            column: ColumnIndex::from(19),
1126        };
1127        let mut builder = PackageDebugInfoBuilder::default();
1128        builder.add_function(function);
1129        let debug_info = builder.build();
1130
1131        let bytes = debug_info.to_bytes();
1132        assert_eq!(bytes[0], 2);
1133        assert!(
1134            bytes.windows(EXPECTED_ROW.len()).any(|window| window == EXPECTED_ROW),
1135            "serialized debug info did not contain the expected function row",
1136        );
1137
1138        let decoded =
1139            PackageDebugInfo::read_from(&mut miden_core::serde::SliceReader::new(&bytes)).unwrap();
1140        assert_eq!(decoded.functions(), [function]);
1141    }
1142
1143    #[test]
1144    fn test_debug_source_graph_roundtrip() {
1145        let mut builder = PackageDebugInfoBuilder::default();
1146        let child = builder
1147            .add_node(DebugSourceNode {
1148                exec_node: MastNodeId::new_unchecked(0),
1149                children: alloc::vec![],
1150                op_start: 0,
1151                op_end: 1,
1152                asm_ops: alloc::vec![],
1153                debug_vars: alloc::vec![],
1154                inline_calls: alloc::vec![],
1155            })
1156            .unwrap();
1157        let root = builder
1158            .add_node(DebugSourceNode {
1159                exec_node: MastNodeId::new_unchecked(1),
1160                children: alloc::vec![child],
1161                op_start: 1,
1162                op_end: 3,
1163                asm_ops: alloc::vec![],
1164                debug_vars: alloc::vec![],
1165                inline_calls: alloc::vec![],
1166            })
1167            .unwrap();
1168        builder.add_root(root);
1169
1170        let debug_info = *builder.build();
1171        let result = roundtrip_debug_info(&debug_info);
1172        assert_eq!(result.nodes().as_slice(), debug_info.nodes().as_slice());
1173        assert_eq!(result.roots(), debug_info.roots());
1174    }
1175
1176    #[test]
1177    fn test_debug_source_metadata_roundtrip() {
1178        let mut builder = PackageDebugInfoBuilder::default();
1179        let location =
1180            Location::new(Uri::new("file://test.masm"), ByteIndex::new(10), ByteIndex::new(14));
1181        let location_idx = builder.add_location(location);
1182        let file_idx = builder.debug_info().locations()[location_idx].file_idx;
1183        let context_name_idx = builder.add_string("test::ctx");
1184        let op_name_idx = builder.add_string("add");
1185        let var_name_idx = builder.add_string("x");
1186        let function_name_idx = builder.add_string("callee");
1187        let function_idx = builder.add_function(DebugFunctionInfo::new(
1188            None,
1189            function_name_idx,
1190            file_idx,
1191            LineNumber::new(10).unwrap(),
1192            ColumnNumber::new(5).unwrap(),
1193            Word::default(),
1194        ));
1195
1196        let root = builder
1197            .add_node(DebugSourceNode {
1198                exec_node: MastNodeId::new_unchecked(0),
1199                children: alloc::vec![],
1200                op_start: 0,
1201                op_end: 3,
1202                asm_ops: alloc::vec![DebugSourceAsmOp::new(
1203                    2,
1204                    Some(location_idx),
1205                    context_name_idx,
1206                    op_name_idx,
1207                    1,
1208                )],
1209                debug_vars: alloc::vec![DebugSourceVar {
1210                    op_idx: 2,
1211                    name_idx: var_name_idx,
1212                    type_id: None,
1213                    arg_idx: None,
1214                    location_idx: None,
1215                    value_location: DebugVarLocation::Stack(0),
1216                }],
1217                inline_calls: alloc::vec![DebugSourceInlineCall {
1218                    op_idx: 2,
1219                    callee_idx: function_idx,
1220                    loc_idx: location_idx,
1221                }],
1222            })
1223            .unwrap();
1224        builder.add_root(root);
1225
1226        let debug_info = *builder.build();
1227        let result = roundtrip_debug_info(&debug_info);
1228        assert_eq!(result.nodes().as_slice(), debug_info.nodes().as_slice());
1229        assert_eq!(result.locations(), debug_info.locations());
1230        assert_eq!(result.functions(), debug_info.functions());
1231        assert_eq!(result.get_string(context_name_idx).as_deref(), Some("test::ctx"));
1232        assert_eq!(result.get_location(location_idx), debug_info.get_location(location_idx));
1233    }
1234
1235    #[test]
1236    fn test_debug_source_locations_are_deduplicated() {
1237        let mut builder = PackageDebugInfoBuilder::default();
1238        let location =
1239            Location::new(Uri::new("file://test.masm"), ByteIndex::new(10), ByteIndex::new(14));
1240        let first_location_idx = builder.add_location(location.clone());
1241        let second_location_idx = builder.add_location(location);
1242        assert_eq!(first_location_idx, second_location_idx);
1243        assert_eq!(builder.debug_info().locations().len(), 1);
1244
1245        let context_name_idx = builder.add_string("test::ctx");
1246        let push_name_idx = builder.add_string("push.1");
1247        let add_name_idx = builder.add_string("add");
1248        let root = builder
1249            .add_node(DebugSourceNode {
1250                exec_node: MastNodeId::new_unchecked(0),
1251                children: alloc::vec![],
1252                op_start: 0,
1253                op_end: 2,
1254                asm_ops: alloc::vec![
1255                    DebugSourceAsmOp::new(
1256                        0,
1257                        Some(first_location_idx),
1258                        context_name_idx,
1259                        push_name_idx,
1260                        1,
1261                    ),
1262                    DebugSourceAsmOp::new(
1263                        1,
1264                        Some(second_location_idx),
1265                        context_name_idx,
1266                        add_name_idx,
1267                        1,
1268                    ),
1269                ],
1270                debug_vars: alloc::vec![],
1271                inline_calls: alloc::vec![],
1272            })
1273            .unwrap();
1274        builder.add_root(root);
1275
1276        let debug_info = *builder.build();
1277        let result = roundtrip_debug_info(&debug_info);
1278        assert_eq!(result.locations().len(), 1);
1279        assert_eq!(
1280            result.source_node(root).unwrap().asm_ops,
1281            debug_info.source_node(root).unwrap().asm_ops
1282        );
1283    }
1284
1285    #[test]
1286    fn test_debug_source_strings_are_deduplicated() {
1287        let mut builder = PackageDebugInfoBuilder::default();
1288        let context_name_idx = builder.add_string("test::ctx");
1289        let same_context_name_idx = builder.add_string("test::ctx");
1290        let add_name_idx = builder.add_string("add");
1291        let same_add_name_idx = builder.add_string("add");
1292        let mul_name_idx = builder.add_string("mul");
1293        let other_context_idx = builder.add_string("test::other");
1294        assert_eq!(context_name_idx, same_context_name_idx);
1295        assert_eq!(add_name_idx, same_add_name_idx);
1296
1297        let root = builder
1298            .add_node(DebugSourceNode {
1299                exec_node: MastNodeId::new_unchecked(0),
1300                children: alloc::vec![],
1301                op_start: 0,
1302                op_end: 3,
1303                asm_ops: alloc::vec![
1304                    DebugSourceAsmOp::new(0, None, context_name_idx, add_name_idx, 1,),
1305                    DebugSourceAsmOp::new(1, None, same_context_name_idx, mul_name_idx, 1,),
1306                    DebugSourceAsmOp::new(2, None, other_context_idx, same_add_name_idx, 1,),
1307                ],
1308                debug_vars: alloc::vec![],
1309                inline_calls: alloc::vec![],
1310            })
1311            .unwrap();
1312        builder.add_root(root);
1313
1314        let debug_info = *builder.build();
1315        let result = roundtrip_debug_info(&debug_info);
1316        assert_eq!(result.strings(), debug_info.strings());
1317        assert_eq!(
1318            result.source_node(root).unwrap().asm_ops,
1319            debug_info.source_node(root).unwrap().asm_ops
1320        );
1321    }
1322
1323    #[test]
1324    fn test_debug_error_messages_roundtrip() {
1325        let mut builder = PackageDebugInfoBuilder::default();
1326        assert!(builder.add_error_message(42, Arc::from("assertion message")));
1327
1328        let debug_info = *builder.build();
1329        let result = roundtrip_debug_info(&debug_info);
1330        assert_eq!(result.error_messages(), debug_info.error_messages());
1331        assert_eq!(result.error_message(42).as_deref(), Some("assertion message"));
1332    }
1333
1334    #[test]
1335    fn test_empty_debug_info_roundtrip() {
1336        let debug_info = PackageDebugInfo::default();
1337        let result = roundtrip_debug_info(&debug_info);
1338        assert!(result.strings().is_empty());
1339        assert!(result.files().is_empty());
1340        assert!(result.locations().is_empty());
1341        assert!(result.types().is_empty());
1342        assert!(result.functions().is_empty());
1343        assert!(result.nodes().is_empty());
1344        assert!(result.roots().is_empty());
1345        assert!(result.error_messages().is_empty());
1346    }
1347
1348    #[test]
1349    fn test_all_primitive_types_roundtrip() {
1350        let mut builder = PackageDebugInfoBuilder::default();
1351
1352        for primitive in [
1353            DebugPrimitiveType::Void,
1354            DebugPrimitiveType::Bool,
1355            DebugPrimitiveType::I8,
1356            DebugPrimitiveType::U8,
1357            DebugPrimitiveType::I16,
1358            DebugPrimitiveType::U16,
1359            DebugPrimitiveType::I32,
1360            DebugPrimitiveType::U32,
1361            DebugPrimitiveType::I64,
1362            DebugPrimitiveType::U64,
1363            DebugPrimitiveType::I128,
1364            DebugPrimitiveType::U128,
1365            DebugPrimitiveType::F32,
1366            DebugPrimitiveType::F64,
1367            DebugPrimitiveType::Felt,
1368            DebugPrimitiveType::Word,
1369            DebugPrimitiveType::U256,
1370        ] {
1371            builder.add_type(DebugTypeInfo::Primitive(primitive));
1372        }
1373
1374        let debug_info = *builder.build();
1375        let result = roundtrip_debug_info(&debug_info);
1376        assert_eq!(result.types(), debug_info.types());
1377    }
1378
1379    #[test]
1380    fn test_function_type_roundtrip() {
1381        let ty = DebugTypeInfo::Function {
1382            return_type_idx: Some(DebugTypeIdx::from(0)),
1383            param_type_indices: alloc::vec![
1384                DebugTypeIdx::from(1),
1385                DebugTypeIdx::from(2),
1386                DebugTypeIdx::from(3)
1387            ],
1388        };
1389        roundtrip(&ty);
1390
1391        let void_fn = DebugTypeInfo::Function {
1392            return_type_idx: None,
1393            param_type_indices: alloc::vec![],
1394        };
1395        roundtrip(&void_fn);
1396    }
1397
1398    #[test]
1399    fn test_file_info_with_checksum_roundtrip() {
1400        let file = DebugFileInfo::new(DebugStringIdx::from(0)).with_checksum([42u8; 32]);
1401        roundtrip(&file);
1402    }
1403
1404    #[test]
1405    fn test_debug_info_v1_is_rejected() {
1406        let bytes = [1];
1407        let mut reader = miden_core::serde::SliceReader::new(&bytes);
1408        let error = PackageDebugInfo::read_from(&mut reader).unwrap_err();
1409        let DeserializationError::InvalidValue(message) = error else {
1410            panic!("expected InvalidValue error");
1411        };
1412        assert!(message.contains("unsupported debug_info version: 1"));
1413    }
1414
1415    #[test]
1416    fn test_debug_info_payload_bounds() {
1417        let bytes = PackageDebugInfo::default().to_bytes();
1418
1419        let mut reader = FixedBudgetReader::new(&bytes, 1);
1420        let error = PackageDebugInfo::read_from(&mut reader).unwrap_err();
1421        let DeserializationError::InvalidValue(message) = error else {
1422            panic!("expected InvalidValue error");
1423        };
1424        assert!(message.contains("package debug info"));
1425        assert!(message.contains("exceeds budget"));
1426
1427        let mut reader = FixedBudgetReader::new(&bytes, bytes.len());
1428        let result = PackageDebugInfo::read_from(&mut reader).unwrap();
1429        assert!(result.nodes().is_empty());
1430    }
1431
1432    #[test]
1433    fn test_debug_info_rejects_truncated_string_table() {
1434        let mut payload = Vec::new();
1435        payload.write_usize(2);
1436
1437        let mut bytes = Vec::new();
1438        bytes.write_u8(DEBUG_INFO_VERSION);
1439        bytes.write_usize(payload.len());
1440        bytes.write_bytes(&payload);
1441
1442        let mut reader = miden_core::serde::SliceReader::new(&bytes);
1443        let error = PackageDebugInfo::read_from(&mut reader).unwrap_err();
1444        let DeserializationError::InvalidValue(message) = error else {
1445            panic!("expected InvalidValue error");
1446        };
1447        assert!(message.contains("debug_info strings count 2"));
1448        assert!(message.contains("exceeds budget"));
1449    }
1450
1451    #[test]
1452    fn test_function_params_bounds() {
1453        let too_many = function_type_bytes(2);
1454        let mut reader = FixedBudgetReader::new(&too_many, 4);
1455        let error = DebugTypeInfo::read_from(&mut reader).unwrap_err();
1456        assert!(matches!(error, DeserializationError::InvalidValue(_)));
1457
1458        let ok = function_type_bytes(1);
1459        let mut reader = FixedBudgetReader::new(&ok, 4);
1460        let ty = DebugTypeInfo::read_from(&mut reader).unwrap();
1461        match ty {
1462            DebugTypeInfo::Function { param_type_indices, .. } => {
1463                assert_eq!(param_type_indices.len(), 1);
1464            },
1465            _ => panic!("expected function type"),
1466        }
1467    }
1468}