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 all `(op_idx, DebugVarId)` pairs for the given node, or an empty vec if the
276    /// node has no debug vars.
277    pub fn debug_vars_for_node(&self, node_id: MastNodeId) -> Vec<(usize, DebugVarId)> {
278        self.op_debug_var_storage.debug_vars_for_node(node_id)
279    }
280
281    /// Returns debug variable IDs for a specific operation within a node.
282    pub fn debug_vars_for_operation(
283        &self,
284        node_id: MastNodeId,
285        local_op_idx: usize,
286    ) -> &[DebugVarId] {
287        self.op_debug_var_storage
288            .debug_var_ids_for_operation(node_id, local_op_idx)
289            .unwrap_or(&[])
290    }
291
292    // DECORATOR MUTATORS
293    // --------------------------------------------------------------------------------------------
294
295    /// Adds a decorator and returns its ID.
296    pub fn add_decorator(&mut self, decorator: Decorator) -> Result<DecoratorId, MastForestError> {
297        self.decorators.push(decorator).map_err(|_| MastForestError::TooManyDecorators)
298    }
299
300    /// Returns a mutable reference the decorator with the given ID, if it exists.
301    pub(super) fn decorator_mut(&mut self, decorator_id: DecoratorId) -> Option<&mut Decorator> {
302        if decorator_id.to_usize() < self.decorators.len() {
303            Some(&mut self.decorators[decorator_id])
304        } else {
305            None
306        }
307    }
308
309    /// Adds node-level decorators (before_enter and after_exit) for the given node.
310    ///
311    /// # Note
312    /// This method does not validate decorator IDs immediately. Validation occurs during
313    /// operations that need to access the actual decorator data (e.g., merging, serialization).
314    pub(super) fn register_node_decorators(
315        &mut self,
316        node_id: MastNodeId,
317        before_enter: &[DecoratorId],
318        after_exit: &[DecoratorId],
319    ) {
320        self.node_decorator_storage
321            .add_node_decorators(node_id, before_enter, after_exit);
322    }
323
324    /// Registers operation-indexed decorators for a node.
325    ///
326    /// This associates already-added decorators with specific operations within a node.
327    pub(crate) fn register_op_indexed_decorators(
328        &mut self,
329        node_id: MastNodeId,
330        decorators_info: Vec<(usize, DecoratorId)>,
331    ) -> Result<(), crate::mast::debuginfo::decorator_storage::DecoratorIndexError> {
332        self.op_decorator_storage.add_decorator_info_for_node(node_id, decorators_info)
333    }
334
335    /// Clears all decorator information while preserving error codes.
336    ///
337    /// This is used when rebuilding decorator information from nodes.
338    pub fn clear_mappings(&mut self) {
339        self.op_decorator_storage = OpToDecoratorIds::new();
340        self.node_decorator_storage.clear();
341    }
342
343    // ASSEMBLY OP ACCESSORS
344    // --------------------------------------------------------------------------------------------
345
346    /// Returns the number of AssemblyOps.
347    pub fn num_asm_ops(&self) -> usize {
348        self.asm_ops.len()
349    }
350
351    /// Returns all AssemblyOps as a slice.
352    pub fn asm_ops(&self) -> &[AssemblyOp] {
353        self.asm_ops.as_slice()
354    }
355
356    /// Returns the AssemblyOp with the given ID, if it exists.
357    pub fn asm_op(&self, asm_op_id: AsmOpId) -> Option<&AssemblyOp> {
358        self.asm_ops.get(asm_op_id)
359    }
360
361    /// Returns the AssemblyOp for a specific operation within a node, if any.
362    pub fn asm_op_for_operation(&self, node_id: MastNodeId, op_idx: usize) -> Option<&AssemblyOp> {
363        let asm_op_id = self.asm_op_storage.asm_op_id_for_operation(node_id, op_idx)?;
364        self.asm_ops.get(asm_op_id)
365    }
366
367    /// Returns the first AssemblyOp for a node, if any.
368    pub fn first_asm_op_for_node(&self, node_id: MastNodeId) -> Option<&AssemblyOp> {
369        let asm_op_id = self.asm_op_storage.first_asm_op_for_node(node_id)?;
370        self.asm_ops.get(asm_op_id)
371    }
372
373    /// Returns all `(op_idx, AsmOpId)` pairs for the given node.
374    pub fn asm_ops_for_node(&self, node_id: MastNodeId) -> Vec<(usize, AsmOpId)> {
375        self.asm_op_storage.asm_ops_for_node(node_id)
376    }
377
378    // ASSEMBLY OP MUTATORS
379    // --------------------------------------------------------------------------------------------
380
381    /// Adds an AssemblyOp and returns its ID.
382    pub fn add_asm_op(&mut self, asm_op: AssemblyOp) -> Result<AsmOpId, MastForestError> {
383        self.asm_ops.push(asm_op).map_err(|_| MastForestError::TooManyDecorators)
384    }
385
386    /// Rewrites the source-backed locations stored in this debug info.
387    pub fn rewrite_source_locations(
388        &mut self,
389        mut rewrite_location: impl FnMut(Location) -> Location,
390        mut rewrite_file_line_col: impl FnMut(FileLineCol) -> FileLineCol,
391    ) {
392        for asm_op in self.asm_ops.iter_mut() {
393            if let Some(location) = asm_op.location().cloned() {
394                asm_op.set_location(rewrite_location(location));
395            }
396        }
397
398        for debug_var in self.debug_vars.iter_mut() {
399            if let Some(location) = debug_var.location().cloned() {
400                debug_var.set_location(rewrite_file_line_col(location));
401            }
402        }
403    }
404
405    /// Registers operation-indexed AssemblyOps for a node.
406    ///
407    /// The `num_operations` parameter must be the total number of operations in the node. This is
408    /// needed to allocate enough space for all operations, even those without AssemblyOps, so that
409    /// lookups at any valid operation index will work correctly.
410    pub fn register_asm_ops(
411        &mut self,
412        node_id: MastNodeId,
413        num_operations: usize,
414        asm_ops: Vec<(usize, AsmOpId)>,
415    ) -> Result<(), AsmOpIndexError> {
416        self.asm_op_storage.add_asm_ops_for_node(node_id, num_operations, asm_ops)
417    }
418
419    /// Remaps the asm_op_storage to use new node IDs after nodes have been removed/reordered.
420    ///
421    /// This should be called after nodes are removed from the MastForest to ensure the asm_op
422    /// storage still references valid node IDs.
423    pub(super) fn remap_asm_op_storage(&mut self, remapping: &BTreeMap<MastNodeId, MastNodeId>) {
424        self.asm_op_storage = self.asm_op_storage.remap_nodes(remapping);
425    }
426
427    /// Remaps the debug var storage to use new node IDs after nodes have been removed/reordered.
428    ///
429    /// This should be called after nodes are removed from the MastForest to ensure the debug
430    /// var storage still references valid node IDs.
431    pub(super) fn remap_debug_var_storage(&mut self, remapping: &BTreeMap<MastNodeId, MastNodeId>) {
432        self.op_debug_var_storage = self.op_debug_var_storage.remap_nodes(remapping);
433    }
434
435    // DEBUG VARIABLE MUTATORS
436    // --------------------------------------------------------------------------------------------
437
438    /// Adds a debug variable and returns its ID.
439    pub fn add_debug_var(
440        &mut self,
441        debug_var: DebugVarInfo,
442    ) -> Result<DebugVarId, MastForestError> {
443        self.debug_vars.push(debug_var).map_err(|_| MastForestError::TooManyDecorators)
444    }
445
446    /// Registers operation-indexed debug variables for a node.
447    ///
448    /// This associates already-added debug variables with specific operations within a node.
449    pub fn register_op_indexed_debug_vars(
450        &mut self,
451        node_id: MastNodeId,
452        debug_vars_info: Vec<(usize, DebugVarId)>,
453    ) -> Result<(), crate::mast::debuginfo::decorator_storage::DecoratorIndexError> {
454        self.op_debug_var_storage.add_debug_var_info_for_node(node_id, debug_vars_info)
455    }
456
457    // ERROR CODE METHODS
458    // --------------------------------------------------------------------------------------------
459
460    /// Returns an error message by code.
461    pub fn error_message(&self, code: u64) -> Option<Arc<str>> {
462        self.error_codes.get(&code).cloned()
463    }
464
465    /// Returns an iterator over error codes.
466    pub fn error_codes(&self) -> impl Iterator<Item = (&u64, &Arc<str>)> {
467        self.error_codes.iter()
468    }
469
470    /// Inserts an error code with its message.
471    pub fn insert_error_code(&mut self, code: u64, msg: Arc<str>) {
472        self.error_codes.insert(code, msg);
473    }
474
475    /// Inserts multiple error codes at once.
476    ///
477    /// This is used when bulk error code insertion is needed.
478    pub fn extend_error_codes<I>(&mut self, error_codes: I)
479    where
480        I: IntoIterator<Item = (u64, Arc<str>)>,
481    {
482        self.error_codes.extend(error_codes);
483    }
484
485    /// Clears all error codes.
486    ///
487    /// This is used when error code information needs to be reset.
488    pub fn clear_error_codes(&mut self) {
489        self.error_codes.clear();
490    }
491
492    // PROCEDURE NAME METHODS
493    // --------------------------------------------------------------------------------------------
494
495    /// Returns the procedure name for the given MAST root digest, if present.
496    pub fn procedure_name(&self, digest: &Word) -> Option<&str> {
497        self.procedure_names.get(&LexicographicWord::from(*digest)).map(|s| s.as_ref())
498    }
499
500    /// Returns an iterator over all (digest, name) pairs.
501    pub fn procedure_names(&self) -> impl Iterator<Item = (Word, &Arc<str>)> {
502        self.procedure_names.iter().map(|(key, name)| (key.into_inner(), name))
503    }
504
505    /// Returns the number of procedure names.
506    pub fn num_procedure_names(&self) -> usize {
507        self.procedure_names.len()
508    }
509
510    /// Inserts a procedure name for the given MAST root digest.
511    pub fn insert_procedure_name(&mut self, digest: Word, name: Arc<str>) {
512        self.procedure_names.insert(LexicographicWord::from(digest), name);
513    }
514
515    /// Inserts multiple procedure names at once.
516    pub fn extend_procedure_names<I>(&mut self, names: I)
517    where
518        I: IntoIterator<Item = (Word, Arc<str>)>,
519    {
520        self.procedure_names
521            .extend(names.into_iter().map(|(d, n)| (LexicographicWord::from(d), n)));
522    }
523
524    /// Clears all procedure names.
525    pub fn clear_procedure_names(&mut self) {
526        self.procedure_names.clear();
527    }
528
529    // VALIDATION
530    // --------------------------------------------------------------------------------------------
531
532    /// Validate the integrity of the DebugInfo structure.
533    ///
534    /// This validates:
535    /// - All CSR structures in op_decorator_storage
536    /// - All CSR structures in node_decorator_storage
537    /// - All CSR structures in asm_op_storage
538    /// - All CSR structures in op_debug_var_storage
539    /// - All decorator IDs reference valid decorators
540    /// - All AsmOpIds reference valid AssemblyOps
541    /// - All debug var IDs reference valid debug vars
542    pub(super) fn validate(&self) -> Result<(), String> {
543        let decorator_count = self.decorators.len();
544        let asm_op_count = self.asm_ops.len();
545
546        // Validate OpToDecoratorIds CSR
547        self.op_decorator_storage.validate_csr(decorator_count)?;
548
549        // Validate NodeToDecoratorIds CSR
550        self.node_decorator_storage.validate_csr(decorator_count)?;
551
552        // Validate OpToAsmOpId CSR
553        self.asm_op_storage.validate_csr(asm_op_count)?;
554
555        // Validate OpToDebugVarIds CSR
556        let debug_var_count = self.debug_vars.len();
557        self.op_debug_var_storage.validate_csr(debug_var_count)?;
558
559        Ok(())
560    }
561
562    // TEST HELPERS
563    // --------------------------------------------------------------------------------------------
564
565    /// Returns the operation decorator storage.
566    #[cfg(test)]
567    pub(crate) fn op_decorator_storage(&self) -> &OpToDecoratorIds {
568        &self.op_decorator_storage
569    }
570}
571
572impl Serializable for DebugInfo {
573    fn write_into<W: ByteWriter>(&self, target: &mut W) {
574        // 1. Serialize decorators (data, string table, infos)
575        let mut decorator_data_builder = DecoratorDataBuilder::new();
576        for decorator in self.decorators.iter() {
577            decorator_data_builder.add_decorator(decorator);
578        }
579        let (decorator_data, decorator_infos, string_table) = decorator_data_builder.finalize();
580
581        decorator_data.write_into(target);
582        string_table.write_into(target);
583        decorator_infos.write_into(target);
584
585        // 2. Serialize error codes
586        let error_codes: BTreeMap<u64, String> =
587            self.error_codes.iter().map(|(k, v)| (*k, v.to_string())).collect();
588        error_codes.write_into(target);
589
590        // 3. Serialize OpToDecoratorIds CSR (dense representation)
591        // Dense representation: serialize indptr arrays as-is (no sparse encoding).
592        // Analysis shows sparse saves <1KB even with 90% empty nodes, not worth complexity.
593        // See measurement: https://gist.github.com/huitseeker/7379e2eecffd7020ae577e986057a400
594        self.op_decorator_storage.write_into(target);
595
596        // 4. Serialize NodeToDecoratorIds CSR (dense representation)
597        self.node_decorator_storage.write_into(target);
598
599        // 5. Serialize procedure names
600        let procedure_names: BTreeMap<Word, String> =
601            self.procedure_names().map(|(k, v)| (k, v.to_string())).collect();
602        procedure_names.write_into(target);
603
604        // 6. Serialize AssemblyOps (data, string table, infos)
605        let mut asm_op_data_builder = AsmOpDataBuilder::new();
606        for asm_op in self.asm_ops.iter() {
607            asm_op_data_builder.add_asm_op(asm_op);
608        }
609        let (asm_op_data, asm_op_infos, asm_op_string_table) = asm_op_data_builder.finalize();
610
611        asm_op_data.write_into(target);
612        asm_op_string_table.write_into(target);
613        asm_op_infos.write_into(target);
614
615        // 7. Serialize OpToAsmOpId CSR (dense representation)
616        self.asm_op_storage.write_into(target);
617
618        // 8. Serialize debug variables
619        self.debug_vars.write_into(target);
620
621        // 9. Serialize OpToDebugVarIds CSR
622        self.op_debug_var_storage.write_into(target);
623    }
624}
625
626impl Deserializable for DebugInfo {
627    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
628        // 1. Read decorator data and string table
629        let decorator_data: Vec<u8> = Deserializable::read_from(source)?;
630        let string_table: StringTable = Deserializable::read_from(source)?;
631        let decorator_infos: Vec<DecoratorInfo> = Deserializable::read_from(source)?;
632
633        // 2. Reconstruct decorators
634        let mut decorators = IndexVec::new();
635        for decorator_info in decorator_infos {
636            let decorator = decorator_info.try_into_decorator(&string_table, &decorator_data)?;
637            decorators.push(decorator).map_err(|_| {
638                DeserializationError::InvalidValue(
639                    "Failed to add decorator to IndexVec".to_string(),
640                )
641            })?;
642        }
643
644        // 3. Read error codes
645        let error_codes_raw: BTreeMap<u64, String> = Deserializable::read_from(source)?;
646        let error_codes: BTreeMap<u64, Arc<str>> =
647            error_codes_raw.into_iter().map(|(k, v)| (k, Arc::from(v.as_str()))).collect();
648
649        // 4. Read OpToDecoratorIds CSR (dense representation)
650        let op_decorator_storage = OpToDecoratorIds::read_from(source, decorators.len())?;
651
652        // 5. Read NodeToDecoratorIds CSR (dense representation)
653        let node_decorator_storage = NodeToDecoratorIds::read_from(source, decorators.len())?;
654
655        // 6. Read procedure names
656        // Note: Procedure name digests are validated at the MastForest level (in
657        // MastForest::validate) to ensure they reference actual procedures in the forest.
658        let procedure_names_raw: BTreeMap<Word, String> = Deserializable::read_from(source)?;
659        let procedure_names: BTreeMap<LexicographicWord, Arc<str>> = procedure_names_raw
660            .into_iter()
661            .map(|(k, v)| (LexicographicWord::from(k), Arc::from(v.as_str())))
662            .collect();
663
664        // 7. Read AssemblyOps (data, string table, infos)
665        let asm_op_data: Vec<u8> = Deserializable::read_from(source)?;
666        let asm_op_string_table: StringTable = Deserializable::read_from(source)?;
667        let asm_op_infos: Vec<AsmOpInfo> = Deserializable::read_from(source)?;
668
669        // 8. Reconstruct AssemblyOps
670        let mut asm_ops = IndexVec::new();
671        for asm_op_info in asm_op_infos {
672            let asm_op = asm_op_info.try_into_asm_op(&asm_op_string_table, &asm_op_data)?;
673            asm_ops.push(asm_op).map_err(|_| {
674                DeserializationError::InvalidValue(
675                    "Failed to add AssemblyOp to IndexVec".to_string(),
676                )
677            })?;
678        }
679
680        // 9. Read OpToAsmOpId CSR (dense representation)
681        let asm_op_storage = OpToAsmOpId::read_from(source, asm_ops.len())?;
682
683        // 10. Read debug variables
684        let debug_vars: IndexVec<DebugVarId, DebugVarInfo> = Deserializable::read_from(source)?;
685
686        // 11. Read OpToDebugVarIds CSR
687        let op_debug_var_storage = OpToDebugVarIds::read_from(source, debug_vars.len())?;
688
689        // 12. Construct and validate DebugInfo
690        let debug_info = DebugInfo {
691            decorators,
692            op_decorator_storage,
693            node_decorator_storage,
694            asm_ops,
695            asm_op_storage,
696            debug_vars,
697            op_debug_var_storage,
698            error_codes,
699            procedure_names,
700        };
701
702        debug_info.validate().map_err(|e| {
703            DeserializationError::InvalidValue(format!("DebugInfo validation failed: {}", e))
704        })?;
705
706        Ok(debug_info)
707    }
708}
709
710impl Default for DebugInfo {
711    fn default() -> Self {
712        Self::new()
713    }
714}