Skip to main content

miden_core/mast/debuginfo/
mod.rs

1//! Debug information management for MAST forests.
2//!
3//! This module provides the [`DebugInfo`] struct which consolidates all debug-related information
4//! for a MAST forest in a single location. This includes:
5//!
6//! - All decorators (debug, trace, and assembly operation metadata)
7//! - Operation-indexed decorator mappings for efficient lookup
8//! - Node-level decorator storage (before_enter/after_exit)
9//! - Error code mappings for descriptive error messages
10//!
11//! The debug info is always available at the `MastForest` level (as per issue 1821), but may be
12//! conditionally included during assembly to maintain backward compatibility. Decorators are only
13//! executed when the processor is running in debug mode, allowing debug information to be available
14//! for debugging and error reporting without impacting performance in production execution.
15//!
16//! # Debug Mode Semantics
17//!
18//! Debug mode is controlled via [`ExecutionOptions`](air::options::ExecutionOptions):
19//! - `with_debugging(true)` enables debug mode explicitly
20//! - `with_tracing()` automatically enables debug mode (tracing requires debug info)
21//! - By default, debug mode is disabled for maximum performance
22//!
23//! When debug mode is disabled:
24//! - Debug decorators are not executed
25//! - Trace decorators are not executed
26//! - Assembly operation decorators are not recorded
27//! - before_enter/after_exit decorators are not executed
28//!
29//! When debug mode is enabled:
30//! - All decorator types are executed according to their semantics
31//! - Debug decorators trigger host callbacks for breakpoints
32//! - Trace decorators trigger host callbacks for tracing
33//! - Assembly operation decorators provide source mapping information
34//! - before_enter/after_exit decorators execute around node execution
35//!
36//! # Production Builds
37//!
38//! The `DebugInfo` can be stripped for production builds using the [`clear()`](Self::clear) method,
39//! which removes decorators while preserving critical information. This allows backward
40//! compatibility while enabling size optimization for deployment.
41
42use alloc::{
43    collections::BTreeMap,
44    string::{String, ToString},
45    sync::Arc,
46    vec::Vec,
47};
48
49use miden_debug_types::{FileLineCol, Location};
50#[cfg(feature = "serde")]
51use serde::{Deserialize, Serialize};
52
53use super::{AsmOpId, Decorator, DecoratorId, MastForestError, MastNodeId};
54use crate::{
55    LexicographicWord, Word,
56    mast::serialization::{
57        StringTable,
58        asm_op::{AsmOpDataBuilder, AsmOpInfo},
59        decorator::{DecoratorDataBuilder, DecoratorInfo},
60    },
61    operations::{AssemblyOp, DebugVarInfo},
62    serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
63    utils::{Idx, IndexVec},
64};
65
66mod asm_op_storage;
67pub use asm_op_storage::{AsmOpIndexError, OpToAsmOpId};
68
69mod decorator_storage;
70pub use decorator_storage::{
71    DecoratedLinks, DecoratedLinksIter, DecoratorIndexError, OpToDecoratorIds,
72};
73
74mod debug_var_storage;
75pub use debug_var_storage::{DebugVarId, OpToDebugVarIds};
76
77mod node_decorator_storage;
78pub use node_decorator_storage::NodeToDecoratorIds;
79
80// DEBUG INFO
81// ================================================================================================
82
83/// Debug information for a MAST forest, containing decorators and error messages.
84#[derive(Debug, Clone, PartialEq, Eq)]
85#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
86pub struct DebugInfo {
87    /// All decorators in the MAST forest (Debug and Trace only, no AsmOp).
88    decorators: IndexVec<DecoratorId, Decorator>,
89
90    /// Efficient access to decorators per operation per node.
91    op_decorator_storage: OpToDecoratorIds,
92
93    /// Efficient storage for node-level decorators (before_enter and after_exit).
94    node_decorator_storage: NodeToDecoratorIds,
95
96    /// All AssemblyOps in the MAST forest.
97    asm_ops: IndexVec<AsmOpId, AssemblyOp>,
98
99    /// Efficient access to AssemblyOps per operation per node.
100    asm_op_storage: OpToAsmOpId,
101
102    /// All debug variable information in the MAST forest.
103    debug_vars: IndexVec<DebugVarId, DebugVarInfo>,
104
105    /// Efficient access to debug variables per operation per node.
106    op_debug_var_storage: OpToDebugVarIds,
107
108    /// Maps error codes to error messages.
109    error_codes: BTreeMap<u64, Arc<str>>,
110
111    /// Maps MAST root digests to procedure names for debugging purposes.
112    #[cfg_attr(feature = "serde", serde(skip))]
113    procedure_names: BTreeMap<LexicographicWord, Arc<str>>,
114}
115
116impl DebugInfo {
117    // CONSTRUCTORS
118    // --------------------------------------------------------------------------------------------
119
120    /// Creates a new empty [DebugInfo].
121    pub fn new() -> Self {
122        Self {
123            decorators: IndexVec::new(),
124            op_decorator_storage: OpToDecoratorIds::new(),
125            node_decorator_storage: NodeToDecoratorIds::new(),
126            asm_ops: IndexVec::new(),
127            asm_op_storage: OpToAsmOpId::new(),
128            debug_vars: IndexVec::new(),
129            op_debug_var_storage: OpToDebugVarIds::new(),
130            error_codes: BTreeMap::new(),
131            procedure_names: BTreeMap::new(),
132        }
133    }
134
135    /// Creates an empty [DebugInfo] with specified capacities.
136    pub fn with_capacity(
137        decorators_capacity: usize,
138        nodes_capacity: usize,
139        operations_capacity: usize,
140        decorator_ids_capacity: usize,
141    ) -> Self {
142        Self {
143            decorators: IndexVec::with_capacity(decorators_capacity),
144            op_decorator_storage: OpToDecoratorIds::with_capacity(
145                nodes_capacity,
146                operations_capacity,
147                decorator_ids_capacity,
148            ),
149            node_decorator_storage: NodeToDecoratorIds::with_capacity(nodes_capacity, 0, 0),
150            asm_ops: IndexVec::new(),
151            asm_op_storage: OpToAsmOpId::new(),
152            debug_vars: IndexVec::new(),
153            op_debug_var_storage: OpToDebugVarIds::new(),
154            error_codes: BTreeMap::new(),
155            procedure_names: BTreeMap::new(),
156        }
157    }
158
159    /// Creates an empty [DebugInfo] with valid CSR structures for N nodes.
160    pub fn empty_for_nodes(num_nodes: usize) -> Self {
161        let node_indptr_for_op_idx = IndexVec::try_from(vec![0; num_nodes + 1])
162            .expect("num_nodes should not exceed u32::MAX");
163
164        let op_decorator_storage =
165            OpToDecoratorIds::from_components(Vec::new(), Vec::new(), node_indptr_for_op_idx)
166                .expect("Empty CSR structure should be valid");
167
168        Self {
169            decorators: IndexVec::new(),
170            op_decorator_storage,
171            node_decorator_storage: NodeToDecoratorIds::new(),
172            asm_ops: IndexVec::new(),
173            asm_op_storage: OpToAsmOpId::new(),
174            debug_vars: IndexVec::new(),
175            op_debug_var_storage: OpToDebugVarIds::new(),
176            error_codes: BTreeMap::new(),
177            procedure_names: BTreeMap::new(),
178        }
179    }
180
181    // PUBLIC ACCESSORS
182    // --------------------------------------------------------------------------------------------
183
184    /// Returns true if this [DebugInfo] has no decorators, asm_ops, debug vars, error codes, or
185    /// procedure names.
186    pub fn is_empty(&self) -> bool {
187        self.decorators.is_empty()
188            && self.asm_ops.is_empty()
189            && self.debug_vars.is_empty()
190            && self.error_codes.is_empty()
191            && self.procedure_names.is_empty()
192    }
193
194    /// Strips all debug information, removing decorators, asm_ops, debug vars, error codes, and
195    /// procedure
196    /// names.
197    ///
198    /// This is used for release builds where debug info is not needed.
199    pub fn clear(&mut self) {
200        self.clear_mappings();
201        self.decorators = IndexVec::new();
202        self.asm_ops = IndexVec::new();
203        self.asm_op_storage = OpToAsmOpId::new();
204        self.debug_vars = IndexVec::new();
205        self.op_debug_var_storage.clear();
206        self.error_codes.clear();
207        self.procedure_names.clear();
208    }
209
210    // DECORATOR ACCESSORS
211    // --------------------------------------------------------------------------------------------
212
213    /// Returns the number of decorators.
214    pub fn num_decorators(&self) -> usize {
215        self.decorators.len()
216    }
217
218    /// Returns all decorators as a slice.
219    pub fn decorators(&self) -> &[Decorator] {
220        self.decorators.as_slice()
221    }
222
223    /// Returns the decorator with the given ID, if it exists.
224    pub fn decorator(&self, decorator_id: DecoratorId) -> Option<&Decorator> {
225        self.decorators.get(decorator_id)
226    }
227
228    /// Returns the before-enter decorators for the given node.
229    pub fn before_enter_decorators(&self, node_id: MastNodeId) -> &[DecoratorId] {
230        self.node_decorator_storage.get_before_decorators(node_id)
231    }
232
233    /// Returns the after-exit decorators for the given node.
234    pub fn after_exit_decorators(&self, node_id: MastNodeId) -> &[DecoratorId] {
235        self.node_decorator_storage.get_after_decorators(node_id)
236    }
237
238    /// Returns decorators for a specific operation within a node.
239    pub fn decorators_for_operation(
240        &self,
241        node_id: MastNodeId,
242        local_op_idx: usize,
243    ) -> &[DecoratorId] {
244        self.op_decorator_storage
245            .decorator_ids_for_operation(node_id, local_op_idx)
246            .unwrap_or(&[])
247    }
248
249    /// Returns decorator links for a node, including operation indices.
250    pub(super) fn decorator_links_for_node(
251        &self,
252        node_id: MastNodeId,
253    ) -> Result<DecoratedLinks<'_>, DecoratorIndexError> {
254        self.op_decorator_storage.decorator_links_for_node(node_id)
255    }
256
257    // DEBUG VARIABLE ACCESSORS
258    // --------------------------------------------------------------------------------------------
259
260    /// Returns the number of debug variables.
261    pub fn num_debug_vars(&self) -> usize {
262        self.debug_vars.len()
263    }
264
265    /// Returns all debug variables as a slice.
266    pub fn debug_vars(&self) -> &[DebugVarInfo] {
267        self.debug_vars.as_slice()
268    }
269
270    /// Returns the debug variable with the given ID, if it exists.
271    pub fn debug_var(&self, debug_var_id: DebugVarId) -> Option<&DebugVarInfo> {
272        self.debug_vars.get(debug_var_id)
273    }
274
275    /// Returns debug variable IDs for a specific operation within a node.
276    pub fn debug_vars_for_operation(
277        &self,
278        node_id: MastNodeId,
279        local_op_idx: usize,
280    ) -> &[DebugVarId] {
281        self.op_debug_var_storage
282            .debug_var_ids_for_operation(node_id, local_op_idx)
283            .unwrap_or(&[])
284    }
285
286    // DECORATOR MUTATORS
287    // --------------------------------------------------------------------------------------------
288
289    /// Adds a decorator and returns its ID.
290    pub fn add_decorator(&mut self, decorator: Decorator) -> Result<DecoratorId, MastForestError> {
291        self.decorators.push(decorator).map_err(|_| MastForestError::TooManyDecorators)
292    }
293
294    /// Returns a mutable reference the decorator with the given ID, if it exists.
295    pub(super) fn decorator_mut(&mut self, decorator_id: DecoratorId) -> Option<&mut Decorator> {
296        if decorator_id.to_usize() < self.decorators.len() {
297            Some(&mut self.decorators[decorator_id])
298        } else {
299            None
300        }
301    }
302
303    /// Adds node-level decorators (before_enter and after_exit) for the given node.
304    ///
305    /// # Note
306    /// This method does not validate decorator IDs immediately. Validation occurs during
307    /// operations that need to access the actual decorator data (e.g., merging, serialization).
308    pub(super) fn register_node_decorators(
309        &mut self,
310        node_id: MastNodeId,
311        before_enter: &[DecoratorId],
312        after_exit: &[DecoratorId],
313    ) {
314        self.node_decorator_storage
315            .add_node_decorators(node_id, before_enter, after_exit);
316    }
317
318    /// Registers operation-indexed decorators for a node.
319    ///
320    /// This associates already-added decorators with specific operations within a node.
321    pub(crate) fn register_op_indexed_decorators(
322        &mut self,
323        node_id: MastNodeId,
324        decorators_info: Vec<(usize, DecoratorId)>,
325    ) -> Result<(), crate::mast::debuginfo::decorator_storage::DecoratorIndexError> {
326        self.op_decorator_storage.add_decorator_info_for_node(node_id, decorators_info)
327    }
328
329    /// Clears all decorator information while preserving error codes.
330    ///
331    /// This is used when rebuilding decorator information from nodes.
332    pub fn clear_mappings(&mut self) {
333        self.op_decorator_storage = OpToDecoratorIds::new();
334        self.node_decorator_storage.clear();
335    }
336
337    // ASSEMBLY OP ACCESSORS
338    // --------------------------------------------------------------------------------------------
339
340    /// Returns the number of AssemblyOps.
341    pub fn num_asm_ops(&self) -> usize {
342        self.asm_ops.len()
343    }
344
345    /// Returns all AssemblyOps as a slice.
346    pub fn asm_ops(&self) -> &[AssemblyOp] {
347        self.asm_ops.as_slice()
348    }
349
350    /// Returns the AssemblyOp with the given ID, if it exists.
351    pub fn asm_op(&self, asm_op_id: AsmOpId) -> Option<&AssemblyOp> {
352        self.asm_ops.get(asm_op_id)
353    }
354
355    /// Returns the AssemblyOp for a specific operation within a node, if any.
356    pub fn asm_op_for_operation(&self, node_id: MastNodeId, op_idx: usize) -> Option<&AssemblyOp> {
357        let asm_op_id = self.asm_op_storage.asm_op_id_for_operation(node_id, op_idx)?;
358        self.asm_ops.get(asm_op_id)
359    }
360
361    /// Returns the first AssemblyOp for a node, if any.
362    pub fn first_asm_op_for_node(&self, node_id: MastNodeId) -> Option<&AssemblyOp> {
363        let asm_op_id = self.asm_op_storage.first_asm_op_for_node(node_id)?;
364        self.asm_ops.get(asm_op_id)
365    }
366
367    // ASSEMBLY OP MUTATORS
368    // --------------------------------------------------------------------------------------------
369
370    /// Adds an AssemblyOp and returns its ID.
371    pub fn add_asm_op(&mut self, asm_op: AssemblyOp) -> Result<AsmOpId, MastForestError> {
372        self.asm_ops.push(asm_op).map_err(|_| MastForestError::TooManyDecorators)
373    }
374
375    /// Rewrites the source-backed locations stored in this debug info.
376    pub fn rewrite_source_locations(
377        &mut self,
378        mut rewrite_location: impl FnMut(Location) -> Location,
379        mut rewrite_file_line_col: impl FnMut(FileLineCol) -> FileLineCol,
380    ) {
381        for asm_op in self.asm_ops.iter_mut() {
382            if let Some(location) = asm_op.location().cloned() {
383                asm_op.set_location(rewrite_location(location));
384            }
385        }
386
387        for debug_var in self.debug_vars.iter_mut() {
388            if let Some(location) = debug_var.location().cloned() {
389                debug_var.set_location(rewrite_file_line_col(location));
390            }
391        }
392    }
393
394    /// Registers operation-indexed AssemblyOps for a node.
395    ///
396    /// The `num_operations` parameter must be the total number of operations in the node. This is
397    /// needed to allocate enough space for all operations, even those without AssemblyOps, so that
398    /// lookups at any valid operation index will work correctly.
399    pub fn register_asm_ops(
400        &mut self,
401        node_id: MastNodeId,
402        num_operations: usize,
403        asm_ops: Vec<(usize, AsmOpId)>,
404    ) -> Result<(), AsmOpIndexError> {
405        self.asm_op_storage.add_asm_ops_for_node(node_id, num_operations, asm_ops)
406    }
407
408    /// Remaps the asm_op_storage to use new node IDs after nodes have been removed/reordered.
409    ///
410    /// This should be called after nodes are removed from the MastForest to ensure the asm_op
411    /// storage still references valid node IDs.
412    pub(super) fn remap_asm_op_storage(&mut self, remapping: &BTreeMap<MastNodeId, MastNodeId>) {
413        self.asm_op_storage = self.asm_op_storage.remap_nodes(remapping);
414    }
415
416    // DEBUG VARIABLE MUTATORS
417    // --------------------------------------------------------------------------------------------
418
419    /// Adds a debug variable and returns its ID.
420    pub fn add_debug_var(
421        &mut self,
422        debug_var: DebugVarInfo,
423    ) -> Result<DebugVarId, MastForestError> {
424        self.debug_vars.push(debug_var).map_err(|_| MastForestError::TooManyDecorators)
425    }
426
427    /// Registers operation-indexed debug variables for a node.
428    ///
429    /// This associates already-added debug variables with specific operations within a node.
430    pub fn register_op_indexed_debug_vars(
431        &mut self,
432        node_id: MastNodeId,
433        debug_vars_info: Vec<(usize, DebugVarId)>,
434    ) -> Result<(), crate::mast::debuginfo::decorator_storage::DecoratorIndexError> {
435        self.op_debug_var_storage.add_debug_var_info_for_node(node_id, debug_vars_info)
436    }
437
438    // ERROR CODE METHODS
439    // --------------------------------------------------------------------------------------------
440
441    /// Returns an error message by code.
442    pub fn error_message(&self, code: u64) -> Option<Arc<str>> {
443        self.error_codes.get(&code).cloned()
444    }
445
446    /// Returns an iterator over error codes.
447    pub fn error_codes(&self) -> impl Iterator<Item = (&u64, &Arc<str>)> {
448        self.error_codes.iter()
449    }
450
451    /// Inserts an error code with its message.
452    pub fn insert_error_code(&mut self, code: u64, msg: Arc<str>) {
453        self.error_codes.insert(code, msg);
454    }
455
456    /// Inserts multiple error codes at once.
457    ///
458    /// This is used when bulk error code insertion is needed.
459    pub fn extend_error_codes<I>(&mut self, error_codes: I)
460    where
461        I: IntoIterator<Item = (u64, Arc<str>)>,
462    {
463        self.error_codes.extend(error_codes);
464    }
465
466    /// Clears all error codes.
467    ///
468    /// This is used when error code information needs to be reset.
469    pub fn clear_error_codes(&mut self) {
470        self.error_codes.clear();
471    }
472
473    // PROCEDURE NAME METHODS
474    // --------------------------------------------------------------------------------------------
475
476    /// Returns the procedure name for the given MAST root digest, if present.
477    pub fn procedure_name(&self, digest: &Word) -> Option<&str> {
478        self.procedure_names.get(&LexicographicWord::from(*digest)).map(|s| s.as_ref())
479    }
480
481    /// Returns an iterator over all (digest, name) pairs.
482    pub fn procedure_names(&self) -> impl Iterator<Item = (Word, &Arc<str>)> {
483        self.procedure_names.iter().map(|(key, name)| (key.into_inner(), name))
484    }
485
486    /// Returns the number of procedure names.
487    pub fn num_procedure_names(&self) -> usize {
488        self.procedure_names.len()
489    }
490
491    /// Inserts a procedure name for the given MAST root digest.
492    pub fn insert_procedure_name(&mut self, digest: Word, name: Arc<str>) {
493        self.procedure_names.insert(LexicographicWord::from(digest), name);
494    }
495
496    /// Inserts multiple procedure names at once.
497    pub fn extend_procedure_names<I>(&mut self, names: I)
498    where
499        I: IntoIterator<Item = (Word, Arc<str>)>,
500    {
501        self.procedure_names
502            .extend(names.into_iter().map(|(d, n)| (LexicographicWord::from(d), n)));
503    }
504
505    /// Clears all procedure names.
506    pub fn clear_procedure_names(&mut self) {
507        self.procedure_names.clear();
508    }
509
510    // VALIDATION
511    // --------------------------------------------------------------------------------------------
512
513    /// Validate the integrity of the DebugInfo structure.
514    ///
515    /// This validates:
516    /// - All CSR structures in op_decorator_storage
517    /// - All CSR structures in node_decorator_storage
518    /// - All CSR structures in asm_op_storage
519    /// - All CSR structures in op_debug_var_storage
520    /// - All decorator IDs reference valid decorators
521    /// - All AsmOpIds reference valid AssemblyOps
522    /// - All debug var IDs reference valid debug vars
523    pub(super) fn validate(&self) -> Result<(), String> {
524        let decorator_count = self.decorators.len();
525        let asm_op_count = self.asm_ops.len();
526
527        // Validate OpToDecoratorIds CSR
528        self.op_decorator_storage.validate_csr(decorator_count)?;
529
530        // Validate NodeToDecoratorIds CSR
531        self.node_decorator_storage.validate_csr(decorator_count)?;
532
533        // Validate OpToAsmOpId CSR
534        self.asm_op_storage.validate_csr(asm_op_count)?;
535
536        // Validate OpToDebugVarIds CSR
537        let debug_var_count = self.debug_vars.len();
538        self.op_debug_var_storage.validate_csr(debug_var_count)?;
539
540        Ok(())
541    }
542
543    // TEST HELPERS
544    // --------------------------------------------------------------------------------------------
545
546    /// Returns the operation decorator storage.
547    #[cfg(test)]
548    pub(crate) fn op_decorator_storage(&self) -> &OpToDecoratorIds {
549        &self.op_decorator_storage
550    }
551}
552
553impl Serializable for DebugInfo {
554    fn write_into<W: ByteWriter>(&self, target: &mut W) {
555        // 1. Serialize decorators (data, string table, infos)
556        let mut decorator_data_builder = DecoratorDataBuilder::new();
557        for decorator in self.decorators.iter() {
558            decorator_data_builder.add_decorator(decorator);
559        }
560        let (decorator_data, decorator_infos, string_table) = decorator_data_builder.finalize();
561
562        decorator_data.write_into(target);
563        string_table.write_into(target);
564        decorator_infos.write_into(target);
565
566        // 2. Serialize error codes
567        let error_codes: BTreeMap<u64, String> =
568            self.error_codes.iter().map(|(k, v)| (*k, v.to_string())).collect();
569        error_codes.write_into(target);
570
571        // 3. Serialize OpToDecoratorIds CSR (dense representation)
572        // Dense representation: serialize indptr arrays as-is (no sparse encoding).
573        // Analysis shows sparse saves <1KB even with 90% empty nodes, not worth complexity.
574        // See measurement: https://gist.github.com/huitseeker/7379e2eecffd7020ae577e986057a400
575        self.op_decorator_storage.write_into(target);
576
577        // 4. Serialize NodeToDecoratorIds CSR (dense representation)
578        self.node_decorator_storage.write_into(target);
579
580        // 5. Serialize procedure names
581        let procedure_names: BTreeMap<Word, String> =
582            self.procedure_names().map(|(k, v)| (k, v.to_string())).collect();
583        procedure_names.write_into(target);
584
585        // 6. Serialize AssemblyOps (data, string table, infos)
586        let mut asm_op_data_builder = AsmOpDataBuilder::new();
587        for asm_op in self.asm_ops.iter() {
588            asm_op_data_builder.add_asm_op(asm_op);
589        }
590        let (asm_op_data, asm_op_infos, asm_op_string_table) = asm_op_data_builder.finalize();
591
592        asm_op_data.write_into(target);
593        asm_op_string_table.write_into(target);
594        asm_op_infos.write_into(target);
595
596        // 7. Serialize OpToAsmOpId CSR (dense representation)
597        self.asm_op_storage.write_into(target);
598
599        // 8. Serialize debug variables
600        self.debug_vars.write_into(target);
601
602        // 9. Serialize OpToDebugVarIds CSR
603        self.op_debug_var_storage.write_into(target);
604    }
605}
606
607impl Deserializable for DebugInfo {
608    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
609        // 1. Read decorator data and string table
610        let decorator_data: Vec<u8> = Deserializable::read_from(source)?;
611        let string_table: StringTable = Deserializable::read_from(source)?;
612        let decorator_infos: Vec<DecoratorInfo> = Deserializable::read_from(source)?;
613
614        // 2. Reconstruct decorators
615        let mut decorators = IndexVec::new();
616        for decorator_info in decorator_infos {
617            let decorator = decorator_info.try_into_decorator(&string_table, &decorator_data)?;
618            decorators.push(decorator).map_err(|_| {
619                DeserializationError::InvalidValue(
620                    "Failed to add decorator to IndexVec".to_string(),
621                )
622            })?;
623        }
624
625        // 3. Read error codes
626        let error_codes_raw: BTreeMap<u64, String> = Deserializable::read_from(source)?;
627        let error_codes: BTreeMap<u64, Arc<str>> =
628            error_codes_raw.into_iter().map(|(k, v)| (k, Arc::from(v.as_str()))).collect();
629
630        // 4. Read OpToDecoratorIds CSR (dense representation)
631        let op_decorator_storage = OpToDecoratorIds::read_from(source, decorators.len())?;
632
633        // 5. Read NodeToDecoratorIds CSR (dense representation)
634        let node_decorator_storage = NodeToDecoratorIds::read_from(source, decorators.len())?;
635
636        // 6. Read procedure names
637        // Note: Procedure name digests are validated at the MastForest level (in
638        // MastForest::validate) to ensure they reference actual procedures in the forest.
639        let procedure_names_raw: BTreeMap<Word, String> = Deserializable::read_from(source)?;
640        let procedure_names: BTreeMap<LexicographicWord, Arc<str>> = procedure_names_raw
641            .into_iter()
642            .map(|(k, v)| (LexicographicWord::from(k), Arc::from(v.as_str())))
643            .collect();
644
645        // 7. Read AssemblyOps (data, string table, infos)
646        let asm_op_data: Vec<u8> = Deserializable::read_from(source)?;
647        let asm_op_string_table: StringTable = Deserializable::read_from(source)?;
648        let asm_op_infos: Vec<AsmOpInfo> = Deserializable::read_from(source)?;
649
650        // 8. Reconstruct AssemblyOps
651        let mut asm_ops = IndexVec::new();
652        for asm_op_info in asm_op_infos {
653            let asm_op = asm_op_info.try_into_asm_op(&asm_op_string_table, &asm_op_data)?;
654            asm_ops.push(asm_op).map_err(|_| {
655                DeserializationError::InvalidValue(
656                    "Failed to add AssemblyOp to IndexVec".to_string(),
657                )
658            })?;
659        }
660
661        // 9. Read OpToAsmOpId CSR (dense representation)
662        let asm_op_storage = OpToAsmOpId::read_from(source, asm_ops.len())?;
663
664        // 10. Read debug variables
665        let debug_vars: IndexVec<DebugVarId, DebugVarInfo> = Deserializable::read_from(source)?;
666
667        // 11. Read OpToDebugVarIds CSR
668        let op_debug_var_storage = OpToDebugVarIds::read_from(source, debug_vars.len())?;
669
670        // 12. Construct and validate DebugInfo
671        let debug_info = DebugInfo {
672            decorators,
673            op_decorator_storage,
674            node_decorator_storage,
675            asm_ops,
676            asm_op_storage,
677            debug_vars,
678            op_debug_var_storage,
679            error_codes,
680            procedure_names,
681        };
682
683        debug_info.validate().map_err(|e| {
684            DeserializationError::InvalidValue(format!("DebugInfo validation failed: {}", e))
685        })?;
686
687        Ok(debug_info)
688    }
689}
690
691impl Default for DebugInfo {
692    fn default() -> Self {
693        Self::new()
694    }
695}