Skip to main content

miden_mast_package/debug_info/
builder.rs

1use alloc::{boxed::Box, sync::Arc};
2
3use miden_assembly_syntax::{
4    Report,
5    ast::types::{EnumRef, RecTypeRef, StructRef},
6};
7use miden_core::mast::MastNodeId;
8use miden_debug_types::{Location, Uri};
9use miden_utils_indexing::{Idx, IndexedVecError};
10
11use super::{
12    DebugErrorMessage, DebugInfo, DebugLoc, DebugLocIdx, DebugSourceNodeId, FunctionInfo,
13    FxHashMap, SourceNode, SourceNodeIdMarker,
14    types::{
15        DebugFileIdx, DebugFileInfo, DebugFunctionIdx, DebugStringIdx, DebugTypeIdx, DebugTypeInfo,
16    },
17};
18use crate::debug_info::{DebugFieldInfo, DebugPrimitiveType, DebugVariantInfo};
19
20// PACKAGE DEBUG INFO BUILDER
21// ================================================================================================
22
23/// This type is used to construct/modify [super::PackageDebugInfo] appended to a Miden package.
24///
25/// It is a type alias for [`DebugInfoBuilder<MastNodeId, DebugSourceNodeId>`] - see its
26/// documentation for more details.
27pub type PackageDebugInfoBuilder = DebugInfoBuilder<MastNodeId, DebugSourceNodeId>;
28
29/// This type is used to construct/modify [DebugInfo] during assembly/packaging.
30///
31/// This type is generic over the index type used for representing execution nodes (unique
32/// references into a [`miden_core::mast::MastForest`]) and source occurrances (a unique set of
33/// debug information attached to an execution node). This allows us to use the same data structure
34/// for representing/constructing debug information during assembly (before execution/source node
35/// indices are finalized) and packaging (once execution/source nodes are finalized).
36///
37/// The [`DebugInfo`] type is heavily reliant on struct-of-arrays layout, with references between
38/// different data types using typed indices rather than pointers or owned references. This requires
39/// care to construct and maintain correctly, so it provides a largely immutable interface, with
40/// responsibility for safely constructing/maintaining it handled by [DebugInfoBuilder].
41pub struct DebugInfoBuilder<Exec: Idx, Src: Idx> {
42    /// Provides uniquing of values stored in the strings table of the underlying `DebugInfo`
43    string_indices: FxHashMap<Arc<str>, DebugStringIdx>,
44    /// Provides uniquing of locations stored in the locations table of the underlying `DebugInfo`
45    location_indices: FxHashMap<DebugLoc, DebugLocIdx>,
46    /// Provides uniquing of locations stored in the locations table of the underlying `DebugInfo`
47    type_indices: FxHashMap<DebugTypeInfo, DebugTypeIdx>,
48    /// Recursive aggregates whose rows are reserved but not yet finalized.
49    ///
50    /// A recursive aggregate's row must exist before its body is registered, so that the
51    /// backedge has an index to point at. Lookups here are cheap: `RecTypeRef` hashes by its
52    /// group's cached hash and compares by pointer first.
53    reserved_recursive_types: FxHashMap<RecTypeRef, DebugTypeIdx>,
54    /// The debug info being built
55    debug_info: Box<DebugInfo<Exec, Src>>,
56}
57
58// FUNDAMENTAL TRAITS
59// ================================================================================================
60
61impl<Exec: Idx, Src: Idx> core::fmt::Debug for DebugInfoBuilder<Exec, Src> {
62    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
63        f.debug_struct("DebugInfoBuilder")
64            .field("string_indices", &self.string_indices)
65            .field("debug_info", &self.debug_info)
66            .finish()
67    }
68}
69
70impl<Exec: Idx, Src: Idx> Default for DebugInfoBuilder<Exec, Src> {
71    fn default() -> Self {
72        Self {
73            string_indices: Default::default(),
74            location_indices: Default::default(),
75            type_indices: Default::default(),
76            reserved_recursive_types: Default::default(),
77            debug_info: Default::default(),
78        }
79    }
80}
81
82impl<Exec: Idx + Clone, Src: Idx + Clone> Clone for DebugInfoBuilder<Exec, Src> {
83    fn clone(&self) -> Self {
84        Self {
85            string_indices: self.string_indices.clone(),
86            location_indices: self.location_indices.clone(),
87            type_indices: self.type_indices.clone(),
88            reserved_recursive_types: self.reserved_recursive_types.clone(),
89            debug_info: self.debug_info.clone(),
90        }
91    }
92}
93
94// TYPED INDEXING
95// ================================================================================================
96
97impl<Exec: Idx, Src: Idx> core::ops::Index<DebugStringIdx> for DebugInfoBuilder<Exec, Src> {
98    type Output = Arc<str>;
99
100    fn index(&self, index: DebugStringIdx) -> &Self::Output {
101        &self.debug_info[index]
102    }
103}
104
105impl<Exec: Idx, Src: Idx> core::ops::Index<DebugFileIdx> for DebugInfoBuilder<Exec, Src> {
106    type Output = DebugFileInfo;
107
108    fn index(&self, index: DebugFileIdx) -> &Self::Output {
109        &self.debug_info[index]
110    }
111}
112
113impl<Exec: Idx, Src: Idx> core::ops::Index<DebugFunctionIdx> for DebugInfoBuilder<Exec, Src> {
114    type Output = FunctionInfo<Src>;
115
116    fn index(&self, index: DebugFunctionIdx) -> &Self::Output {
117        &self.debug_info[index]
118    }
119}
120
121impl<Exec: Idx, Src: Idx> core::ops::Index<DebugLocIdx> for DebugInfoBuilder<Exec, Src> {
122    type Output = DebugLoc;
123
124    fn index(&self, index: DebugLocIdx) -> &Self::Output {
125        &self.debug_info[index]
126    }
127}
128
129impl<Exec: Idx, Src: Idx> core::ops::Index<DebugTypeIdx> for DebugInfoBuilder<Exec, Src> {
130    type Output = DebugTypeInfo;
131
132    fn index(&self, index: DebugTypeIdx) -> &Self::Output {
133        &self.debug_info[index]
134    }
135}
136
137impl<Exec: Idx, Src: SourceNodeIdMarker> core::ops::Index<Src> for DebugInfoBuilder<Exec, Src> {
138    type Output = SourceNode<Exec, Src>;
139
140    fn index(&self, index: Src) -> &Self::Output {
141        &self.debug_info[index]
142    }
143}
144
145impl<Exec: Idx, Src: SourceNodeIdMarker> core::ops::IndexMut<Src> for DebugInfoBuilder<Exec, Src> {
146    fn index_mut(&mut self, index: Src) -> &mut Self::Output {
147        &mut self.debug_info.nodes[index]
148    }
149}
150
151// CONSTRUCTION
152// ================================================================================================
153
154impl<Exec: Idx, Src: Idx> From<Box<DebugInfo<Exec, Src>>> for DebugInfoBuilder<Exec, Src> {
155    fn from(debug_info: Box<DebugInfo<Exec, Src>>) -> Self {
156        use hashbrown::hash_map::Entry;
157
158        let mut string_indices = FxHashMap::default();
159        for (i, string) in debug_info.strings().iter().enumerate() {
160            if let Entry::Vacant(entry) = string_indices.entry(string.clone()) {
161                let idx = DebugStringIdx::from(i as u32);
162                entry.insert(idx);
163            }
164        }
165        let mut location_indices = FxHashMap::default();
166        for (i, loc) in debug_info.locations().iter().enumerate() {
167            if let Entry::Vacant(entry) = location_indices.entry(*loc) {
168                let idx = DebugLocIdx::from(i as u32);
169                entry.insert(idx);
170            }
171        }
172        let mut type_indices = FxHashMap::default();
173        for (i, ty) in debug_info.types().iter().enumerate() {
174            if let Entry::Vacant(entry) = type_indices.entry(ty.clone()) {
175                let idx = DebugTypeIdx::from(i as u32);
176                entry.insert(idx);
177            }
178        }
179        Self {
180            string_indices,
181            location_indices,
182            type_indices,
183            reserved_recursive_types: Default::default(),
184            debug_info,
185        }
186    }
187}
188
189impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
190    /// Finalize construction of the underlying `DebugInfo` and return it
191    ///
192    /// NOTE: `DebugInfo` is a very large type, so it is heap-allocated and returned via `Box`
193    #[inline]
194    pub fn build(self) -> Box<DebugInfo<Exec, Src>> {
195        self.debug_info
196    }
197}
198
199// ACCESSORS
200// ================================================================================================
201
202impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
203    /// Get a reference to the current state of the `DebugInfo` being built
204    pub fn debug_info(&self) -> &DebugInfo<Exec, Src> {
205        &self.debug_info
206    }
207
208    /// Get a mutable reference to the current state of the `DebugInfo` being built
209    pub fn debug_info_mut(&mut self) -> &mut DebugInfo<Exec, Src> {
210        &mut self.debug_info
211    }
212}
213
214// STRINGS
215// ================================================================================================
216
217impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
218    /// Gets a string by index.
219    pub fn get_string(&self, idx: DebugStringIdx) -> Option<Arc<str>> {
220        self.debug_info.strings.get(idx).cloned()
221    }
222
223    /// Gets the [DebugStringIdx] for a string, if it is already interned by the builder
224    pub fn get_string_index(&self, s: &str) -> Option<DebugStringIdx> {
225        self.string_indices.get(s).copied()
226    }
227
228    /// Adds a string to the string table and returns its index.
229    ///
230    /// Strings are uniqued/interned - so adding the same string twice will return the same index
231    pub fn add_string(&mut self, s: impl Into<Arc<str>>) -> DebugStringIdx {
232        let s = s.into();
233        if let Some(exists) = self.string_indices.get(&s).copied() {
234            return exists;
235        }
236        let idx = self.debug_info.strings.push(s.clone()).expect("too many strings");
237        self.string_indices.insert(s, idx);
238        idx
239    }
240}
241
242// SOURCE FILES
243// ================================================================================================
244
245impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
246    /// Gets the [DebugFileIdx] for a source file whose URI is `uri`, if it is recorded in the
247    /// debug info built so far.
248    pub fn get_file_index_by_uri(&self, uri: &Uri) -> Option<DebugFileIdx> {
249        self.debug_info.get_file_index_by_uri(uri)
250    }
251
252    pub fn get_file_index_by_path_index(&self, path_idx: DebugStringIdx) -> Option<DebugFileIdx> {
253        self.debug_info
254            .files
255            .iter()
256            .position(|file| file.path_idx == path_idx)
257            .map(|pos| DebugFileIdx::from(pos as u32))
258    }
259
260    /// Adds a file to the file table under `uri`, with an optional checksum, and returns its index.
261    ///
262    /// If the same `uri` and `checksum` pair is already recorded, then the previously recorded
263    /// index is returned
264    pub fn add_file(&mut self, uri: Uri, checksum: Option<[u8; 32]>) -> DebugFileIdx {
265        let path_idx = self.add_string(uri);
266        self.add_file_info(
267            DebugFileInfo::new(path_idx)
268                .with_checksum(checksum.unwrap_or(DebugFileInfo::EMPTY_CHECKSUM)),
269        )
270    }
271
272    /// Adds a file to the file table and returns its index.
273    pub fn add_file_info(&mut self, file: DebugFileInfo) -> DebugFileIdx {
274        assert!(
275            self.debug_info.strings.get(file.path_idx).is_some(),
276            "invalid path string index"
277        );
278        if let Some(idx) = self.debug_info.files.iter().position(|existing| existing == &file) {
279            return DebugFileIdx::from(idx as u32);
280        }
281        self.debug_info.files.push(file).expect("too many files")
282    }
283}
284
285// LOCATIONS
286// ================================================================================================
287
288impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
289    /// Adds `loc` to the set of unique source locations maintained by the builder
290    pub fn add_location(&mut self, loc: Location) -> DebugLocIdx {
291        let path_idx = self.add_string(loc.uri().clone());
292        let file_idx = self
293            .get_file_index_by_path_index(path_idx)
294            .unwrap_or_else(|| self.add_file_info(DebugFileInfo::new(path_idx)));
295        self.add_location_info(DebugLoc { file_idx, start: loc.start, end: loc.end })
296    }
297
298    /// Adds a source location whose file is already registered with this builder.
299    ///
300    /// This form preserves the exact file-table relationship when importing debug information
301    /// that may contain multiple records for the same path with different checksums.
302    pub fn add_location_info(&mut self, loc: DebugLoc) -> DebugLocIdx {
303        use hashbrown::hash_map::Entry;
304
305        assert!(self.debug_info.files.get(loc.file_idx).is_some(), "invalid source file index");
306
307        match self.location_indices.entry(loc) {
308            Entry::Occupied(entry) => *entry.get(),
309            Entry::Vacant(entry) => {
310                let index = self.debug_info.locations.push(loc).expect("too many locations");
311                entry.insert(index);
312                index
313            },
314        }
315    }
316}
317
318// TYPE INFO
319// ================================================================================================
320
321impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
322    /// Adds `ty` to the set of unique types maintained by the builder, and returns its index
323    pub fn add_type(&mut self, ty: DebugTypeInfo) -> DebugTypeIdx {
324        use hashbrown::hash_map::Entry;
325        match self.type_indices.entry(ty.clone()) {
326            Entry::Occupied(entry) => *entry.get(),
327            Entry::Vacant(entry) => {
328                let index = self.debug_info.types.push(ty).expect("too many types");
329                entry.insert(index);
330                index
331            },
332        }
333    }
334
335    /// Reserves a row for a type whose body has not been registered yet.
336    ///
337    /// The row is deliberately kept out of the uniquing map: it holds a placeholder, and uniquing
338    /// against a placeholder would let an unrelated type collapse onto it. Only
339    /// [`Self::finish_reserved_type`] makes the row visible to uniquing.
340    fn reserve_type(&mut self) -> DebugTypeIdx {
341        self.debug_info.types.push(DebugTypeInfo::Unknown).expect("too many types")
342    }
343
344    /// Fills in a row reserved by [`Self::reserve_type`], and makes it available for uniquing.
345    fn finish_reserved_type(&mut self, index: DebugTypeIdx, ty: DebugTypeInfo) {
346        self.debug_info.types[index] = ty.clone();
347        self.type_indices.entry(ty).or_insert(index);
348    }
349
350    /// Overwrite a row, for tests that need to build a cyclic type table by hand.
351    #[cfg(test)]
352    pub(crate) fn replace_type_for_test(&mut self, index: DebugTypeIdx, ty: DebugTypeInfo) {
353        self.debug_info.types[index] = ty;
354    }
355
356    /// Appends a type without uniquing it, while keeping the builder's type cache coherent.
357    ///
358    /// This is used when importing a complete type table: all output indices must be reserved
359    /// before any row is rewritten so that forward and cyclic type references remain valid.
360    pub(crate) fn push_type(&mut self, ty: DebugTypeInfo) -> DebugTypeIdx {
361        let index = self.debug_info.types.push(ty.clone()).expect("too many types");
362        self.type_indices.entry(ty).or_insert(index);
363        index
364    }
365}
366
367// FUNCTION INFO
368// ================================================================================================
369
370impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
371    /// Look up the index of a function info record by its [`miden_assembly_syntax::Path`].
372    ///
373    /// The path is matched against both the linkage name and the source name of each function.
374    /// Linkage name matches take precedence over source name matches.
375    pub fn get_function_index_by_path(
376        &self,
377        path: &miden_assembly_syntax::Path,
378    ) -> Option<DebugFunctionIdx> {
379        let path = path.as_str();
380        let mut name_match = None;
381        for (pos, f) in self.debug_info.functions.iter().enumerate() {
382            if let Some(linkage_name_idx) = f.linkage_name_idx.into_option()
383                && self.debug_info[linkage_name_idx].as_ref() == path
384            {
385                return Some(DebugFunctionIdx::from(pos as u32));
386            }
387            if name_match.is_none() && self.debug_info[f.name_idx].as_ref() == path {
388                name_match = Some(pos);
389            }
390        }
391        name_match.map(|pos| DebugFunctionIdx::from(pos as u32))
392    }
393
394    /// Adds a function to the function table.
395    pub fn add_function(&mut self, func: FunctionInfo<Src>) -> DebugFunctionIdx {
396        self.debug_info.functions.push(func).expect("too many functions")
397    }
398
399    /// Sets the `source_node` field of the [`FunctionInfo`] referred to by `index`
400    pub fn set_function_source_node(&mut self, index: DebugFunctionIdx, node: Src) {
401        self.debug_info.functions[index].source_node = Some(node).into();
402    }
403}
404
405// ERROR MESSAGES
406// ================================================================================================
407
408impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
409    /// Add an error message record keyed by `err_code`.
410    ///
411    /// Returns `true` if `err_code` was not previously registered, otherwise `false`
412    pub fn add_error_message(&mut self, err_code: u64, message: Arc<str>) -> bool {
413        if !self.debug_info.error_messages.iter().any(|msg| msg.err_code == err_code) {
414            let message = self.add_string(message);
415            self.debug_info.error_messages.push(DebugErrorMessage::new(err_code, message));
416            true
417        } else {
418            false
419        }
420    }
421
422    /// Add an error message like `add_error_message`, but use a [DebugStringIdx] for the
423    /// error message string.
424    ///
425    /// This function asserts that `message` exists in the debug info strings table, and will panic
426    /// if it doesn't
427    pub fn add_error_message_with_index(&mut self, err_code: u64, message: DebugStringIdx) {
428        assert!(
429            self.debug_info.get_string(message).is_some(),
430            "invalid string index for message"
431        );
432        if !self.debug_info.error_messages.iter().any(|msg| msg.err_code == err_code) {
433            self.debug_info.error_messages.push(DebugErrorMessage::new(err_code, message));
434        }
435    }
436}
437
438// SOURCE NODES
439// ================================================================================================
440
441impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
442    /// Add `node` to the set of sources nodes in the debug info source graph
443    pub fn add_node(&mut self, mut node: SourceNode<Exec, Src>) -> Result<Src, IndexedVecError> {
444        assert!(node.op_end >= node.op_start);
445        assert!(node.children.iter().copied().all(|n| self.debug_info.source_node(n).is_some()));
446        node.asm_ops.sort_unstable_by_key(|row| row.op_idx);
447        self.debug_info.nodes.push(node)
448    }
449
450    /// Get a reference to the set of source node indices which correspond to procedure roots
451    pub fn roots(&self) -> &[Src] {
452        self.debug_info.roots()
453    }
454
455    /// Mark `node` as a procedure root
456    pub fn add_root(&mut self, node: Src) {
457        assert!(self.debug_info.source_node(node).is_some());
458        if !self.debug_info.roots.contains(&node) {
459            self.debug_info.roots.push(node);
460        }
461    }
462}
463
464impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
465    /// This visits a type exported or used in a procedure signature, and emits records to the
466    /// provided debug types section corresponding to it.
467    ///
468    /// The declared name and type expression can be optionally provided to give additional useful
469    /// context to the debug info type produced, e.g. type name, field names, etc.
470    /// Build the debug record for a struct, registering its field types.
471    ///
472    /// This is separate from [`Self::register_debug_type`] so a recursive aggregate can fill in a
473    /// row that was reserved before its body was walked, rather than appending a second row.
474    fn struct_debug_type(
475        &mut self,
476        declared_name: Option<DebugStringIdx>,
477        declared_ty: Option<&miden_assembly_syntax::ast::TypeExpr>,
478        struct_ty: &miden_assembly_syntax::ast::types::StructType,
479    ) -> Result<DebugTypeInfo, Report> {
480        use miden_assembly_syntax::ast::TypeExpr;
481
482        let declared_field_tys = declared_ty.and_then(|t| match t {
483            TypeExpr::Struct(t) => Some(&t.fields),
484            _ => None,
485        });
486        let mut fields = vec![];
487        for (i, field) in struct_ty.fields().iter().enumerate() {
488            let decl = declared_field_tys.and_then(|fields| fields.get(i));
489            let field_name =
490                decl.map(|decl| decl.name.clone().into_inner()).or_else(|| field.name.clone());
491            let declared_ty = decl.map(|decl| &decl.ty);
492            let field_type_name = declared_type_name(declared_ty);
493            let field_type_name = field_type_name.map(|name| self.add_string(name));
494            let type_idx = self.register_debug_type(field_type_name, declared_ty, &field.ty)?;
495            let name_idx = self.add_string(field_name.unwrap_or_else(|| format!("{i}").into()));
496            fields.push(DebugFieldInfo { name_idx, type_idx, offset: field.offset });
497        }
498        let struct_name =
499            declared_name.or_else(|| struct_ty.name().map(|name| self.add_string(name)));
500        let name_idx = struct_name.unwrap_or_else(|| self.add_string("<anon>"));
501        let size = u32::try_from(struct_ty.size()).map_err(|_| {
502            if let Some(declared_name) = struct_name.as_ref() {
503                Report::msg(format!(
504                    "invalid struct type '{}': struct is too large",
505                    self.get_string(*declared_name).unwrap()
506                ))
507            } else {
508                Report::msg("invalid struct type: struct is too large")
509            }
510        })?;
511        Ok(DebugTypeInfo::Struct { name_idx, size, fields })
512    }
513
514    /// Build the debug record for an enum, registering its discriminant and payload types.
515    fn enum_debug_type(
516        &mut self,
517        enum_ty: &miden_assembly_syntax::ast::types::EnumType,
518    ) -> Result<DebugTypeInfo, Report> {
519        let discrim_ty = self.register_debug_type(None, None, enum_ty.discriminant())?;
520        let name_idx = self.add_string(enum_ty.name().clone());
521        let size = u32::try_from(enum_ty.size_in_bytes()).map_err(|_| {
522            Report::msg(format!("invalid enum type '{}': enum is too large", enum_ty.name()))
523        })?;
524        let variants = enum_ty
525            .variant_offsets()
526            .zip(enum_ty.discriminant_values())
527            .map(|((payload_offset, variant), discriminant)| {
528                let name_idx = self.add_string(variant.name.clone());
529                let type_idx = variant
530                    .value
531                    .as_ref()
532                    .map(|ty| self.register_debug_type(None, None, ty))
533                    .transpose()?;
534                let payload_offset = variant.value.as_ref().map(|_| payload_offset);
535                Ok(DebugVariantInfo {
536                    name_idx,
537                    type_idx,
538                    payload_offset,
539                    discriminant,
540                })
541            })
542            .collect::<Result<_, Report>>()?;
543        Ok(DebugTypeInfo::Enum {
544            name_idx,
545            size,
546            discriminant_type_idx: discrim_ty,
547            variants,
548        })
549    }
550
551    pub fn register_debug_type(
552        &mut self,
553        declared_name: Option<DebugStringIdx>,
554        declared_ty: Option<&miden_assembly_syntax::ast::TypeExpr>,
555        ty: &miden_assembly_syntax::ast::types::Type,
556    ) -> Result<DebugTypeIdx, Report> {
557        use miden_assembly_syntax::ast::{
558            TypeExpr,
559            types::{StructType, Type},
560        };
561        Ok(match ty {
562            Type::I1 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Bool)),
563            Type::I8 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I8)),
564            Type::U8 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::U8)),
565            Type::I16 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I16)),
566            Type::U16 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::U16)),
567            Type::I32 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I32)),
568            Type::U32 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::U32)),
569            Type::I64 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I64)),
570            Type::U64 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::U64)),
571            Type::I128 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I128)),
572            Type::U128 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::U128)),
573            Type::Felt => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Felt)),
574            Type::F64 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::F64)),
575            Type::U256 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::U256)),
576            Type::Unknown => self.add_type(DebugTypeInfo::Unknown),
577            Type::Never => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Void)),
578            Type::Variadic => self.add_type(DebugTypeInfo::Variadic),
579            Type::Ptr(ptr) => {
580                let pointee_name = declared_ty.and_then(|t| match t {
581                    TypeExpr::Ptr(p) => match p.pointee.as_ref() {
582                        TypeExpr::Ref(p) => Some(Arc::from(p.inner().as_str())),
583                        _ => None,
584                    },
585                    _ => None,
586                });
587                let pointee_name = pointee_name.map(|name| self.add_string(name));
588                let pointee_decl = declared_ty.and_then(|t| match t {
589                    TypeExpr::Ptr(p) => Some(p.pointee.as_ref()),
590                    _ => None,
591                });
592                let pointee_type_idx =
593                    self.register_debug_type(pointee_name, pointee_decl, ptr.pointee())?;
594                self.add_type(DebugTypeInfo::Pointer { pointee_type_idx })
595            },
596            Type::Array(array) => {
597                let element_name = declared_ty.and_then(|t| match t {
598                    TypeExpr::Array(array) => match array.elem.as_ref() {
599                        TypeExpr::Ref(t) => Some(Arc::from(t.inner().as_str())),
600                        _ => None,
601                    },
602                    _ => None,
603                });
604                let element_name = element_name.map(|name| self.add_string(name));
605                let element_decl = declared_ty.and_then(|t| match t {
606                    TypeExpr::Array(p) => Some(p.elem.as_ref()),
607                    _ => None,
608                });
609                let element_type_idx =
610                    self.register_debug_type(element_name, element_decl, array.element_type())?;
611                let count = u32::try_from(array.len())
612                    .map_err(|_| Report::msg("array type is too large"))?;
613                self.add_type(DebugTypeInfo::Array { element_type_idx, count: Some(count) })
614            },
615            Type::List(element_ty) => {
616                // A list is emitted as an array with no fixed element count, which is what
617                // recovery expects. Emitting the fat pointer's `{ len, ptr }` layout as a
618                // synthetic struct instead would not round-trip, as such a record is
619                // indistinguishable from an ordinary struct on the way back.
620                //
621                // `TypeExpr` cannot express a list, so there is never a declared type or name
622                // to propagate here.
623                let element_type_idx = self.register_debug_type(None, None, element_ty)?;
624                self.add_type(DebugTypeInfo::Array { element_type_idx, count: None })
625            },
626            // A recursive aggregate must have a row before its body is registered, so that the
627            // backedge has somewhere to point. Reserve first, register the body -- which will
628            // find the reservation when it reaches the backedge -- then fill the row in.
629            Type::Struct(StructRef::Rec(rec)) | Type::Enum(EnumRef::Rec(rec)) => {
630                if let Some(reserved) = self.reserved_recursive_types.get(rec) {
631                    return Ok(*reserved);
632                }
633
634                let reserved = self.reserve_type();
635                self.reserved_recursive_types.insert(rec.clone(), reserved);
636
637                let body = match ty {
638                    Type::Struct(ty) => {
639                        self.struct_debug_type(declared_name, declared_ty, &ty.get())
640                    },
641                    Type::Enum(ty) => self.enum_debug_type(&ty.get()),
642                    _ => unreachable!("matched a recursive struct or enum above"),
643                };
644
645                self.reserved_recursive_types.remove(rec);
646                // On failure the row keeps the placeholder it was reserved with, so nothing
647                // half-built is reachable.
648                self.finish_reserved_type(reserved, body?);
649                reserved
650            },
651            Type::Struct(struct_ty) => {
652                let info = self.struct_debug_type(declared_name, declared_ty, &struct_ty.get())?;
653                self.add_type(info)
654            },
655            Type::Enum(enum_ty) => {
656                let info = self.enum_debug_type(&enum_ty.get())?;
657                self.add_type(info)
658            },
659            Type::Function(fty) => {
660                let return_type_index = match fty.results() {
661                    [] => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Void)),
662                    [ty] => self.register_debug_type(None, None, ty)?,
663                    types => {
664                        let ty = StructType::new(types.iter().cloned());
665                        let size = u32::try_from(ty.size()).map_err(|_| {
666                        if let Some(declared_name) = declared_name.as_ref() {
667                            Report::msg(format!(
668                                "invalid signature for '{declared_name}': return type is too big"
669                            ))
670                        } else {
671                            Report::msg("invalid signature: return type is too big")
672                        }
673                    })?;
674                        let mut fields = vec![];
675                        for (i, field) in ty.fields().iter().enumerate() {
676                            let name_idx = self.add_string(format!("{i}"));
677                            let type_idx = self.register_debug_type(None, None, &field.ty)?;
678                            fields.push(DebugFieldInfo {
679                                name_idx,
680                                type_idx,
681                                offset: field.offset,
682                            });
683                        }
684                        let name_idx = self.add_string("<anon>");
685                        self.add_type(DebugTypeInfo::Struct { name_idx, size, fields })
686                    },
687                };
688                let mut param_type_indices = vec![];
689                for param in fty.params() {
690                    param_type_indices.push(self.register_debug_type(None, None, param)?);
691                }
692                self.add_type(DebugTypeInfo::Function {
693                    return_type_idx: Some(return_type_index),
694                    param_type_indices,
695                })
696            },
697        })
698    }
699}
700
701fn declared_type_name(
702    declared_ty: Option<&miden_assembly_syntax::ast::TypeExpr>,
703) -> Option<Arc<str>> {
704    use miden_assembly_syntax::ast::TypeExpr;
705    match declared_ty? {
706        TypeExpr::Ref(path) => Some(Arc::from(path.inner().as_str())),
707        TypeExpr::Struct(ty) => ty.name.as_ref().map(|name| name.clone().into_inner()),
708        TypeExpr::Primitive(_) | TypeExpr::Ptr(_) | TypeExpr::Array(_) => None,
709    }
710}
711
712#[cfg(test)]
713mod tests {
714    use alloc::sync::Arc;
715
716    use miden_assembly_syntax::{
717        Path,
718        ast::types::{CallConv, EnumType, FunctionType, StructType, Type, Variant},
719    };
720    use miden_core::Word;
721    use miden_debug_types::{ColumnNumber, LineNumber, Uri};
722    use miden_utils_indexing::Idx;
723
724    use super::*;
725
726    #[test]
727    fn registers_c_like_enum_debug_type() {
728        let mut builder = PackageDebugInfoBuilder::default();
729        let enum_ty = EnumType::new(
730            Arc::from("Status"),
731            Type::U16,
732            [
733                Variant::c_like(Arc::from("Ok"), Some(200)),
734                Variant::c_like(Arc::from("NotFound"), Some(404)),
735            ],
736        )
737        .unwrap();
738        let ty = Type::from(Arc::new(enum_ty));
739
740        let type_idx = builder.register_debug_type(None, None, &ty).unwrap();
741
742        let DebugTypeInfo::Enum {
743            name_idx,
744            size,
745            discriminant_type_idx,
746            variants,
747        } = &builder[type_idx]
748        else {
749            panic!("expected enum debug type");
750        };
751        assert_eq!(builder.get_string(*name_idx).as_deref(), Some("Status"));
752        assert_eq!(*size, 2);
753        assert_eq!(
754            &builder[*discriminant_type_idx],
755            &DebugTypeInfo::Primitive(DebugPrimitiveType::U16)
756        );
757        assert_eq!(variants.len(), 2);
758        assert_eq!(builder.get_string(variants[0].name_idx).as_deref(), Some("Ok"));
759        assert_eq!(variants[0].type_idx, None);
760        assert_eq!(variants[0].payload_offset, None);
761        assert_eq!(variants[0].discriminant, 200);
762        assert_eq!(builder.get_string(variants[1].name_idx).as_deref(), Some("NotFound"));
763        assert_eq!(variants[1].type_idx, None);
764        assert_eq!(variants[1].payload_offset, None);
765        assert_eq!(variants[1].discriminant, 404);
766    }
767
768    #[test]
769    fn registers_payload_enum_debug_type() {
770        let mut builder = PackageDebugInfoBuilder::default();
771        let enum_ty = EnumType::new(
772            Arc::from("OptionU32"),
773            Type::U8,
774            [
775                Variant::c_like(Arc::from("None"), Some(0)),
776                Variant::new(Arc::from("Some"), Type::U32, Some(1)),
777            ],
778        )
779        .unwrap();
780        let ty = Type::from(Arc::new(enum_ty));
781
782        let type_idx = builder.register_debug_type(None, None, &ty).unwrap();
783
784        let DebugTypeInfo::Enum { variants, .. } = &builder[type_idx] else {
785            panic!("expected enum debug type");
786        };
787        assert_eq!(variants.len(), 2);
788        assert_eq!(variants[0].type_idx, None);
789        let payload_type_idx = variants[1].type_idx.expect("Some variant should have payload");
790        assert_eq!(&builder[payload_type_idx], &DebugTypeInfo::Primitive(DebugPrimitiveType::U32));
791        assert_eq!(variants[1].payload_offset, Some(4));
792        assert_eq!(variants[1].discriminant, 1);
793    }
794
795    #[test]
796    fn function_debug_types_preserve_resolved_struct_metadata() {
797        let felt_wrapper = Type::from(Arc::new(StructType::named(
798            "felt-wrapper".into(),
799            [(Arc::from("inner"), Type::Felt)],
800        )));
801        let account_id = Type::from(Arc::new(StructType::named(
802            "account-id".into(),
803            [(Arc::from("prefix"), felt_wrapper.clone()), (Arc::from("suffix"), felt_wrapper)],
804        )));
805        let function = Type::Function(Arc::new(FunctionType::new(
806            CallConv::ComponentModel,
807            [account_id.clone()],
808            [account_id],
809        )));
810        let mut builder = PackageDebugInfoBuilder::default();
811
812        let function_name = builder.add_string("take-account-id");
813        let function_idx = builder
814            .register_debug_type(Some(function_name), None, &function)
815            .expect("function type should register");
816
817        let (return_type_idx, param_type_idx) = match &builder[function_idx] {
818            DebugTypeInfo::Function {
819                return_type_idx: Some(return_type_idx),
820                param_type_indices,
821            } => {
822                assert_eq!(param_type_indices.len(), 1);
823                (*return_type_idx, param_type_indices[0])
824            },
825            other => panic!("expected function debug type, got {other:?}"),
826        };
827
828        assert_struct_debug_type(
829            &builder,
830            param_type_idx,
831            "account-id",
832            &[("prefix", "felt-wrapper"), ("suffix", "felt-wrapper")],
833        );
834        assert_struct_debug_type(
835            &builder,
836            return_type_idx,
837            "account-id",
838            &[("prefix", "felt-wrapper"), ("suffix", "felt-wrapper")],
839        );
840    }
841
842    fn assert_struct_debug_type(
843        builder: &PackageDebugInfoBuilder,
844        type_idx: DebugTypeIdx,
845        expected_name: &str,
846        expected_fields: &[(&str, &str)],
847    ) {
848        let DebugTypeInfo::Struct { name_idx, fields, .. } = &builder[type_idx] else {
849            panic!("expected struct debug type");
850        };
851
852        assert_eq!(builder[*name_idx].as_ref(), expected_name);
853        assert_eq!(fields.len(), expected_fields.len());
854        for (field, (expected_name, expected_type_name)) in fields.iter().zip(expected_fields) {
855            assert_eq!(builder[field.name_idx].as_ref(), *expected_name);
856
857            let DebugTypeInfo::Struct { name_idx, .. } = &builder[field.type_idx] else {
858                panic!("expected struct field type");
859            };
860            assert_eq!(builder[*name_idx].as_ref(), *expected_type_name);
861        }
862    }
863
864    #[test]
865    fn test_debug_info_string_dedup() {
866        let mut builder = PackageDebugInfoBuilder::default();
867
868        let idx1 = builder.add_string(Arc::from("test.rs"));
869        let idx2 = builder.add_string(Arc::from("main.rs"));
870        let idx3 = builder.add_string(Arc::from("test.rs")); // Duplicate
871
872        assert_eq!(idx1.to_usize(), 0);
873        assert_eq!(idx2.to_usize(), 1);
874        assert_eq!(idx3.to_usize(), 0); // Should return same index
875        assert_eq!(builder.string_indices.len(), 2);
876        assert_eq!(builder.debug_info.strings.len(), 2);
877    }
878
879    fn add_test_function(
880        builder: &mut PackageDebugInfoBuilder,
881        name: &str,
882        linkage_name: Option<&str>,
883    ) -> DebugFunctionIdx {
884        let file_idx = builder.add_file(Uri::new("test.masm"), None);
885        let line = LineNumber::new(1).unwrap();
886        let column = ColumnNumber::new(1).unwrap();
887        let name_idx = builder.add_string(name);
888        let func = FunctionInfo::new(None, name_idx, file_idx, line, column, Word::default());
889        let func = match linkage_name {
890            Some(linkage_name) => {
891                let linkage_name_idx = builder.add_string(linkage_name);
892                func.with_linkage_name(linkage_name_idx)
893            },
894            None => func,
895        };
896        builder.add_function(func)
897    }
898
899    #[test]
900    fn function_path_lookup_no_match() {
901        let mut builder = PackageDebugInfoBuilder::default();
902        add_test_function(&mut builder, "::module::plain", None);
903        add_test_function(&mut builder, "duplicate", Some("::module::linked"));
904
905        assert_eq!(builder.get_function_index_by_path(Path::new("::module::missing")), None);
906    }
907
908    #[test]
909    fn function_path_lookup_matches_name() {
910        let mut builder = PackageDebugInfoBuilder::default();
911        let plain_function_idx = add_test_function(&mut builder, "::module::plain", None);
912
913        assert_eq!(
914            builder.get_function_index_by_path(Path::new("::module::plain")),
915            Some(plain_function_idx),
916        );
917    }
918
919    #[test]
920    fn function_path_lookup_matches_linkage_name() {
921        let mut builder = PackageDebugInfoBuilder::default();
922        let linked_function_idx =
923            add_test_function(&mut builder, "duplicate", Some("::module::linked"));
924
925        assert_eq!(
926            builder.get_function_index_by_path(Path::new("::module::linked")),
927            Some(linked_function_idx),
928        );
929    }
930
931    #[test]
932    fn function_path_lookup_matches_source_name_of_linked_function() {
933        let mut builder = PackageDebugInfoBuilder::default();
934        let linked_function_idx =
935            add_test_function(&mut builder, "::module::source", Some("::module::linked"));
936
937        assert_eq!(
938            builder.get_function_index_by_path(Path::new("::module::source")),
939            Some(linked_function_idx),
940        );
941    }
942
943    #[test]
944    fn function_path_lookup_prefers_linkage_name() {
945        let mut builder = PackageDebugInfoBuilder::default();
946        // The name-matching function comes first in the table, but the linkage name match must
947        // still take precedence.
948        add_test_function(&mut builder, "::module::shared", None);
949        let linked_function_idx =
950            add_test_function(&mut builder, "duplicate", Some("::module::shared"));
951
952        assert_eq!(
953            builder.get_function_index_by_path(Path::new("::module::shared")),
954            Some(linked_function_idx),
955        );
956    }
957}