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