Skip to main content

miden_processor/fast/
debug.rs

1use alloc::{
2    collections::{BTreeMap, BTreeSet},
3    sync::Arc,
4    vec,
5    vec::Vec,
6};
7
8use miden_core::{
9    Word,
10    mast::{MastForest, MastNodeExt, MastNodeId},
11};
12use miden_mast_package::debug_info::{
13    DebugFunctionIdx, DebugFunctionInfo, DebugSourceNode, DebugSourceNodeId, DebugStringIdx,
14    PackageDebugInfo,
15};
16
17use crate::{Continuation, ResumeContext, continuation_stack::DebugActivationId};
18
19/// Evidence used to recover a source-level frame. Inferred frames are best-effort and may omit
20/// optimized callers; consumers must distinguish them from frames identified by source identity.
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum DebugFrameOrigin {
23    /// A unique function references the selected source occurrence and its executable root.
24    Source,
25    /// A retained operation range matches a function's assembly-operation metadata.
26    InferredRange,
27    /// The selected source occurrence's assembly context uniquely names a function.
28    InferredContext,
29    /// An external boundary's inline rows identify a caller with a retained canonical definition.
30    InferredInline,
31}
32
33/// A recovered invocation of a debug function, without additional serialized frame metadata.
34#[derive(Clone, Debug)]
35pub struct DebugCallFrame {
36    debug_info: Arc<PackageDebugInfo>,
37    function_idx: DebugFunctionIdx,
38    source_node_id: DebugSourceNodeId,
39    range_start: u32,
40    activation: DebugActivationId,
41    inherited_inline_calls: usize,
42    origin: DebugFrameOrigin,
43}
44
45impl DebugCallFrame {
46    pub fn function_idx(&self) -> DebugFunctionIdx {
47        self.function_idx
48    }
49    pub fn function(&self) -> &DebugFunctionInfo {
50        &self.debug_info[self.function_idx]
51    }
52    pub fn debug_info(&self) -> &PackageDebugInfo {
53        &self.debug_info
54    }
55    pub fn inherited_inline_calls(&self) -> usize {
56        self.inherited_inline_calls
57    }
58    pub fn origin(&self) -> DebugFrameOrigin {
59        self.origin
60    }
61    pub fn is_inferred(&self) -> bool {
62        self.origin != DebugFrameOrigin::Source
63    }
64
65    /// Compares invocation identities, rather than names, so recursive activations stay separate.
66    pub fn is_same_frame(&self, other: &Self) -> bool {
67        Arc::ptr_eq(&self.debug_info, &other.debug_info)
68            && self.function_idx == other.function_idx
69            && self.source_node_id == other.source_node_id
70            && self.range_start == other.range_start
71            && self.activation == other.activation
72    }
73}
74
75#[derive(Clone)]
76struct FunctionRange {
77    function: DebugFunctionIdx,
78    start: u32,
79    end: u32,
80    signature: Signature,
81    /// Explicit inline boundaries retain ownership even when merging re-batches their padding.
82    explicit_boundary: bool,
83}
84
85impl FunctionRange {
86    fn matches_source(&self, node: &DebugSourceNode) -> bool {
87        let start = node.asm_ops.partition_point(|row| row.op_idx < self.start);
88        let end = node.asm_ops.partition_point(|row| row.op_idx < self.end);
89        let rows = &node.asm_ops[start..end];
90        rows.len() == self.signature.len()
91            && rows.iter().zip(&self.signature).all(
92                |(row, (offset, context, operation, cycles))| {
93                    (self.explicit_boundary || row.op_idx - self.start == *offset)
94                        && (row.context_name_idx, row.op_name_idx, row.num_cycles)
95                            == (*context, *operation, *cycles)
96                },
97            )
98    }
99}
100
101type Signature = Vec<(u32, DebugStringIdx, DebugStringIdx, u8)>;
102
103fn signature(node: &DebugSourceNode) -> Signature {
104    node.asm_ops
105        .iter()
106        .map(|row| {
107            (
108                row.op_idx.saturating_sub(node.op_start),
109                row.context_name_idx,
110                row.op_name_idx,
111                row.num_cycles,
112            )
113        })
114        .collect()
115}
116
117/// Active inline depth at every boundary. Build once per source node so many adjacent execs
118/// do not require scanning the entire inline table for each recovered range.
119fn inline_depths(node: &DebugSourceNode) -> BTreeMap<u32, (usize, usize)> {
120    let mut events = BTreeMap::<u32, (usize, usize, usize)>::new();
121    for row in &node.inline_calls {
122        if row.op_idx == row.op_end {
123            events.entry(row.op_idx).or_default().2 += 1;
124        } else {
125            events.entry(row.op_idx).or_default().0 += 1;
126            events.entry(row.op_end).or_default().1 += 1;
127        }
128    }
129    let mut active = 0;
130    events
131        .into_iter()
132        .map(|(position, (starts, ends, points))| {
133            active += starts;
134            active -= ends;
135            (position, (active, points))
136        })
137        .collect()
138}
139
140struct FunctionIndex {
141    info: Arc<PackageDebugInfo>,
142    forest: Arc<MastForest>,
143    sources: BTreeMap<DebugSourceNodeId, Vec<DebugFunctionIdx>>,
144    names: BTreeMap<Arc<str>, Vec<DebugFunctionIdx>>,
145    ranges: BTreeMap<MastNodeId, Vec<FunctionRange>>,
146    entry_inline_counts: BTreeMap<DebugFunctionIdx, usize>,
147    entry_names: BTreeMap<DebugSourceNodeId, Option<DebugStringIdx>>,
148    inline_depths: BTreeMap<DebugSourceNodeId, BTreeMap<u32, (usize, usize)>>,
149}
150
151impl FunctionIndex {
152    fn new(info: Arc<PackageDebugInfo>, forest: Arc<MastForest>) -> Self {
153        let mut index = Self {
154            info,
155            forest,
156            sources: BTreeMap::new(),
157            names: BTreeMap::new(),
158            ranges: BTreeMap::new(),
159            entry_inline_counts: BTreeMap::new(),
160            entry_names: BTreeMap::new(),
161            inline_depths: BTreeMap::new(),
162        };
163        let mut root_nodes = BTreeMap::<Word, MastNodeId>::new();
164        for root in index.forest.procedure_roots() {
165            root_nodes.entry(index.forest[*root].digest()).or_insert(*root);
166        }
167        let mut default_sources = BTreeMap::<MastNodeId, &DebugSourceNode>::new();
168        let mut named_sources = BTreeMap::<(MastNodeId, DebugStringIdx), &DebugSourceNode>::new();
169        let rank = |node: &DebugSourceNode| {
170            (node.asm_ops.len(), core::cmp::Reverse(node.inline_calls.len()))
171        };
172        for node in index.info.nodes() {
173            default_sources
174                .entry(node.exec_node)
175                .and_modify(|current| {
176                    if rank(node) >= rank(current) {
177                        *current = node;
178                    }
179                })
180                .or_insert(node);
181            for row in &node.asm_ops {
182                named_sources
183                    .entry((node.exec_node, row.context_name_idx))
184                    .and_modify(|current| {
185                        if rank(node) >= rank(current) {
186                            *current = node;
187                        }
188                    })
189                    .or_insert(node);
190            }
191        }
192        for position in 0..index.info.nodes().len() {
193            let mut source = DebugSourceNodeId::from(position as u32);
194            let mut path = Vec::new();
195            let name = loop {
196                if let Some(name) = index.entry_names.get(&source) {
197                    break *name;
198                }
199                if path.len() >= index.info.nodes().len() {
200                    break None;
201                }
202                path.push(source);
203                let node = &index.info[source];
204                if let Some(row) = node.asm_ops.first() {
205                    break Some(row.context_name_idx);
206                }
207                let Some(child) = node.children.first() else {
208                    break None;
209                };
210                source = *child;
211            };
212            for source in path {
213                index.entry_names.insert(source, name);
214            }
215        }
216        let mut signatures = BTreeMap::<Signature, Vec<DebugFunctionIdx>>::new();
217        let mut function_signatures = BTreeMap::<DebugFunctionIdx, Signature>::new();
218        for (position, function) in index.info.functions().iter().enumerate() {
219            let function_idx = DebugFunctionIdx::from(position as u32);
220            if let Some(source) = function.source_node.into_option() {
221                index.sources.entry(source).or_default().push(function_idx);
222            }
223            for name_idx in [Some(function.name_idx), function.linkage_name_idx.into_option()]
224                .into_iter()
225                .flatten()
226            {
227                if let Some(name) = index.info.get_string(name_idx) {
228                    let names = index.names.entry(name).or_default();
229                    if names.last() != Some(&function_idx) {
230                        names.push(function_idx);
231                    }
232                }
233            }
234            let Some(root) = root_nodes.get(&function.mast_root) else {
235                continue;
236            };
237            let canonical = [Some(function.name_idx), function.linkage_name_idx.into_option()]
238                .into_iter()
239                .flatten()
240                .filter_map(|name| named_sources.get(&(*root, name)).copied())
241                .max_by_key(|node| rank(node));
242            if let Some(node) = canonical.or_else(|| default_sources.get(root).copied()) {
243                index.entry_inline_counts.insert(
244                    function_idx,
245                    node.inline_calls.iter().filter(|row| row.op_idx == node.op_start).count(),
246                );
247                if canonical.is_some() && !node.asm_ops.is_empty() {
248                    let signature = signature(node);
249                    function_signatures.insert(function_idx, signature.clone());
250                    signatures.entry(signature).or_default().push(function_idx);
251                }
252            }
253        }
254        let mut seen_ranges = BTreeSet::new();
255        for node in index.info.nodes().iter().filter(|node| !node.asm_ops.is_empty()) {
256            let Some(functions) = signatures.get(&signature(node)) else {
257                continue;
258            };
259            let [function] = functions.as_slice() else {
260                continue;
261            };
262            let range = FunctionRange {
263                function: *function,
264                start: node.op_start,
265                end: node.op_end,
266                signature: signature(node),
267                explicit_boundary: false,
268            };
269            let ranges = index.ranges.entry(node.exec_node).or_default();
270            if seen_ranges.insert((node.exec_node, range.function, range.start, range.end)) {
271                ranges.push(range);
272            }
273        }
274        // An optimized exec may retain only its inline boundary, without a separate source
275        // slice. Validate that explicit function identity against its complete executable
276        // signature before recovering it: source-language inline rows alone are not invocations.
277        // Re-batching may move padding inside that explicit boundary. Generic source slices still
278        // require exact offsets so a caller's generated prefix cannot become part of the callee.
279        for (position, node) in index.info.nodes().iter().enumerate() {
280            if !node.inline_calls.is_empty() {
281                index
282                    .inline_depths
283                    .insert(DebugSourceNodeId::from(position as u32), inline_depths(node));
284            }
285            for row in &node.inline_calls {
286                let Some(signature) = function_signatures.get(&row.callee_idx) else {
287                    continue;
288                };
289                let range = FunctionRange {
290                    function: row.callee_idx,
291                    start: row.op_idx,
292                    end: row.op_end,
293                    signature: signature.clone(),
294                    explicit_boundary: true,
295                };
296                if range.matches_source(node)
297                    && seen_ranges.insert((node.exec_node, range.function, range.start, range.end))
298                {
299                    index.ranges.entry(node.exec_node).or_default().push(range);
300                }
301            }
302        }
303        for ranges in index.ranges.values_mut() {
304            ranges.sort_by_key(|range| (range.start, core::cmp::Reverse(range.end)));
305        }
306        index
307    }
308
309    fn inline_depth_at(&self, source: DebugSourceNodeId, operation: u32) -> usize {
310        self.inline_depths
311            .get(&source)
312            .and_then(|depths| depths.range(..=operation).next_back())
313            .map_or(0, |(position, (active, points))| {
314                active + if *position == operation { *points } else { 0 }
315            })
316    }
317
318    fn source_function(&self, source: DebugSourceNodeId) -> Option<DebugFunctionIdx> {
319        let node = self.info.source_node(source)?;
320        let digest = self.forest[node.exec_node].digest();
321        let mut candidates = self
322            .sources
323            .get(&source)?
324            .iter()
325            .copied()
326            .filter(|function| self.info[*function].mast_root == digest);
327        let first = candidates.next()?;
328        candidates.next().is_none().then_some(first)
329    }
330
331    fn external_caller_functions<'a>(
332        &'a self,
333        node: &'a DebugSourceNode,
334    ) -> impl Iterator<Item = (DebugFunctionIdx, u32, usize)> + 'a {
335        let external_digest = self.forest[node.exec_node]
336            .is_external()
337            .then(|| self.forest[node.exec_node].digest());
338        node.inline_calls.iter().rev().filter_map(move |row| {
339            if !row.contains_operation(node.op_start) {
340                return None;
341            }
342            let function = self.info.get_function(row.callee_idx)?;
343            let canonical = self.info.source_node(function.source_node.into_option()?)?;
344            if canonical.exec_node != node.exec_node || Some(function.mast_root) != external_digest
345            {
346                return None;
347            }
348            let own_inline = canonical
349                .inline_calls
350                .iter()
351                .filter(|row| row.contains_operation(canonical.op_start))
352                .count();
353            Some((row.callee_idx, row.op_idx, own_inline))
354        })
355    }
356
357    fn context_function(&self, node: &DebugSourceNode, operation: u32) -> Option<DebugFunctionIdx> {
358        let row = node.asm_op_for_operation(operation)?;
359        if operation >= row.op_idx.saturating_add(u32::from(row.num_cycles)) {
360            return None;
361        }
362        let name = self.info.get_string(row.context_name_idx)?;
363        let [function] = self.names.get(&name)?.as_slice() else {
364            return None;
365        };
366        Some(*function)
367    }
368
369    fn entry_function(&self, source: DebugSourceNodeId) -> Option<DebugFunctionIdx> {
370        let name = self.info.get_string((*self.entry_names.get(&source)?)?)?;
371        let [function] = self.names.get(&name)?.as_slice() else {
372            return None;
373        };
374        let node = self.info.source_node(source)?;
375        (self.info[*function].mast_root == self.forest[node.exec_node].digest())
376            .then_some(*function)
377    }
378}
379
380/// Caches indices derived from existing function records and source nodes. Retain this across
381/// debugger steps and discard it when the debug session ends. It does not alter VM execution.
382#[derive(Default)]
383pub struct DebugCallFrameResolver {
384    indices: Vec<FunctionIndex>,
385}
386
387impl DebugCallFrameResolver {
388    pub fn new() -> Self {
389        Self::default()
390    }
391
392    fn index(&mut self, info: &Arc<PackageDebugInfo>, forest: &Arc<MastForest>) -> &FunctionIndex {
393        let position = self
394            .indices
395            .iter()
396            .position(|index| Arc::ptr_eq(&index.info, info) && Arc::ptr_eq(&index.forest, forest));
397        let position = position.unwrap_or_else(|| {
398            self.indices.push(FunctionIndex::new(info.clone(), forest.clone()));
399            self.indices.len() - 1
400        });
401        &self.indices[position]
402    }
403
404    /// Recover the active call chain for the next clock. Optimized `exec` boundaries may only be
405    /// inferable from ranges or assembly context; inspect each frame's origin before presenting it.
406    pub fn resolve(&mut self, context: &ResumeContext) -> Vec<DebugCallFrame> {
407        let continuations =
408            context.continuation_stack.iter_with_source_node_ids().collect::<Vec<_>>();
409        let next_count = context.continuation_stack.iter_continuations_for_next_clock().count();
410        let next_start = continuations.len().saturating_sub(next_count);
411        let mut owners = vec![None; continuations.len()];
412        let mut info = context.package_debug_info.clone();
413        let mut forest = context.current_forest.clone();
414        let mut inline_depth = context.inline_call_contexts.len();
415        for (position, (continuation, _)) in continuations.iter().enumerate().rev() {
416            if let Continuation::EnterForest {
417                forest: caller_forest,
418                package_debug_info,
419                inline_context_depth,
420                ..
421            } = continuation
422            {
423                info = package_debug_info.clone();
424                forest = caller_forest.clone();
425                inline_depth = *inline_context_depth;
426            }
427            if matches!(continuation, Continuation::FinishDyn(_)) {
428                inline_depth = inline_depth.saturating_sub(1);
429            }
430            let inherited = context.inline_call_contexts
431                [..inline_depth.min(context.inline_call_contexts.len())]
432                .iter()
433                .filter_map(Option::as_ref)
434                .map(|entry| entry.inline_calls().count())
435                .sum::<usize>();
436            owners[position] = info.as_ref().map(|info| (info.clone(), forest.clone(), inherited));
437        }
438        let mut frames = Vec::<DebugCallFrame>::new();
439        for (position, (continuation, source)) in continuations.iter().enumerate() {
440            let ancestor = matches!(
441                continuation,
442                Continuation::FinishJoin(_)
443                    | Continuation::FinishSplit(_)
444                    | Continuation::FinishLoop(_)
445                    | Continuation::FinishCall(_)
446                    | Continuation::FinishDyn(_)
447                    | Continuation::EnterForest { .. }
448            );
449            if position > next_start || (!ancestor && position != next_start) {
450                continue;
451            }
452            let (Some((info, forest, inherited)), Some(source)) = (&owners[position], source)
453            else {
454                continue;
455            };
456            let Some(node) = info.source_node(*source) else {
457                continue;
458            };
459            let Some(activation) = context.continuation_stack.debug_activation_at(position) else {
460                continue;
461            };
462            let operation = match continuation {
463                Continuation::ResumeBasicBlock { node_id, batch_index, op_idx_in_batch } => {
464                    let block = forest[*node_id].unwrap_basic_block();
465                    (block
466                        .op_batches()
467                        .iter()
468                        .take(*batch_index)
469                        .map(|batch| batch.ops().len())
470                        .sum::<usize>()
471                        + op_idx_in_batch) as u32
472                },
473                Continuation::Respan { node_id, batch_index } => forest[*node_id]
474                    .unwrap_basic_block()
475                    .op_batches()
476                    .iter()
477                    .take(*batch_index)
478                    .map(|batch| batch.ops().len() as u32)
479                    .sum(),
480                Continuation::FinishBasicBlock(node_id) => {
481                    forest[*node_id].unwrap_basic_block().num_operations().saturating_sub(1)
482                },
483                _ => node.op_start,
484            };
485            let index = self.index(info, forest);
486            let exact = index.source_function(*source);
487            if let Some(function) = exact.or_else(|| index.entry_function(*source)) {
488                let own_inline = index.entry_inline_counts.get(&function).copied().unwrap_or(0);
489                let inherited = inherited
490                    + node
491                        .inline_calls
492                        .iter()
493                        .filter(|row| row.op_idx == node.op_start)
494                        .count()
495                        .saturating_sub(own_inline);
496                frames.push(frame(
497                    info,
498                    function,
499                    *source,
500                    node.op_start,
501                    activation.clone(),
502                    inherited,
503                    if exact.is_some() {
504                        DebugFrameOrigin::Source
505                    } else {
506                        DebugFrameOrigin::InferredContext
507                    },
508                ));
509            }
510            // External exec copies have no assembly operations or canonical source identity.
511            // Their explicit inline rows still identify caller-owned definitions, ordered
512            // from outermost to innermost. Imported declarations without a local definition
513            // belong to the loaded package's frames instead.
514            for (function, start, own_inline) in index.external_caller_functions(node) {
515                if frames.last().is_some_and(|frame| {
516                    frame.function_idx == function && Arc::ptr_eq(&frame.debug_info, info)
517                }) {
518                    continue;
519                }
520                let inherited = inherited
521                    + node
522                        .inline_calls
523                        .iter()
524                        .filter(|row| row.contains_operation(node.op_start))
525                        .count()
526                        .saturating_sub(own_inline);
527                frames.push(frame(
528                    info,
529                    function,
530                    *source,
531                    start,
532                    activation.clone(),
533                    inherited,
534                    DebugFrameOrigin::InferredInline,
535                ));
536            }
537            if position != next_start {
538                continue;
539            }
540            if let Some(ranges) = index.ranges.get(&node.exec_node) {
541                let mut enclosing_end = node.op_end;
542                for range in
543                    ranges.iter().filter(|range| range.start <= operation && operation < range.end)
544                {
545                    if range.start < node.op_start
546                        || range.end > enclosing_end
547                        || !range.matches_source(node)
548                        || frames.last().is_some_and(|frame| {
549                            frame.function_idx == range.function
550                                && Arc::ptr_eq(&frame.debug_info, info)
551                        })
552                    {
553                        continue;
554                    }
555                    enclosing_end = range.end;
556                    frames.push(frame(
557                        info,
558                        range.function,
559                        *source,
560                        range.start,
561                        activation.clone(),
562                        inherited
563                            + index.inline_depth_at(*source, range.start).saturating_sub(
564                                index
565                                    .entry_inline_counts
566                                    .get(&range.function)
567                                    .copied()
568                                    .unwrap_or(0),
569                            ),
570                        DebugFrameOrigin::InferredRange,
571                    ));
572                }
573            }
574            if let Some(function) = index.context_function(node, operation)
575                && !frames.last().is_some_and(|frame| {
576                    frame.function_idx == function && Arc::ptr_eq(&frame.debug_info, info)
577                })
578            {
579                let inline_calls =
580                    node.inline_calls.iter().filter(|row| row.contains_operation(operation));
581                // Rows are innermost-first. The exec boundary separates the callee's changing
582                // source-inline context from its inherited callers, including the boundary itself.
583                let own_inline =
584                    inline_calls.clone().position(|row| row.callee_idx == function).unwrap_or_else(
585                        || index.entry_inline_counts.get(&function).copied().unwrap_or(0),
586                    );
587                let inherited = inherited + inline_calls.count().saturating_sub(own_inline);
588                frames.push(frame(
589                    info,
590                    function,
591                    *source,
592                    node.op_start,
593                    activation,
594                    inherited,
595                    DebugFrameOrigin::InferredContext,
596                ));
597            }
598        }
599        frames
600    }
601}
602
603impl ResumeContext {
604    /// Recover frames without retaining an index. Repeated queries should use a
605    /// [`DebugCallFrameResolver`] to avoid rebuilding the derived lookup tables.
606    pub fn debug_call_frames(&self) -> Vec<DebugCallFrame> {
607        DebugCallFrameResolver::new().resolve(self)
608    }
609}
610
611fn frame(
612    info: &Arc<PackageDebugInfo>,
613    function_idx: DebugFunctionIdx,
614    source_node_id: DebugSourceNodeId,
615    range_start: u32,
616    activation: DebugActivationId,
617    inherited_inline_calls: usize,
618    origin: DebugFrameOrigin,
619) -> DebugCallFrame {
620    DebugCallFrame {
621        debug_info: info.clone(),
622        function_idx,
623        source_node_id,
624        range_start,
625        activation,
626        inherited_inline_calls,
627        origin,
628    }
629}
630
631#[cfg(test)]
632mod tests {
633    use alloc::{boxed::Box, format};
634
635    use miden_assembly::{Assembler, DefaultSourceManager};
636    use miden_core::serde::Serializable;
637    use miden_mast_package::debug_info::PackageDebugInfoBuilder;
638
639    use super::*;
640
641    #[test]
642    fn inline_depth_index_respects_adjacent_nested_and_point_ranges() {
643        use miden_mast_package::debug_info::{DebugLocIdx, DebugSourceInlineCall};
644        let package = Assembler::new(Arc::new(DefaultSourceManager::default()))
645            .assemble_program("program", "begin push.7 drop end")
646            .unwrap();
647        let info = package.debug_info().unwrap().unwrap();
648        let mut node = info.nodes().iter().next().unwrap().clone();
649        node.inline_calls = [(0, 10), (0, 5), (5, 10), (5, 5)]
650            .into_iter()
651            .map(|(op_idx, op_end)| DebugSourceInlineCall {
652                op_idx,
653                op_end,
654                callee_idx: DebugFunctionIdx::from(0_u32),
655                loc_idx: DebugLocIdx::from(0_u32),
656            })
657            .collect();
658        let depths = inline_depths(&node);
659        assert_eq!(depths[&0], (2, 0));
660        assert_eq!(depths[&5], (2, 1));
661        assert_eq!(depths[&10], (0, 0));
662    }
663
664    #[test]
665    fn frame_index_handles_many_sources_and_functions_sharing_an_execution_node() {
666        let package = Assembler::new(Arc::new(DefaultSourceManager::default()))
667            .assemble_program("program", "begin push.7 drop end")
668            .unwrap();
669        let info = package.debug_info().unwrap().unwrap();
670        let template = info.functions()[0];
671        let node = info[template.source_node.into_option().unwrap()].clone();
672        let mut builder = PackageDebugInfoBuilder::from(Box::new(info));
673        for position in 0..50_000 {
674            let name = builder.add_string(format!("function_{position}"));
675            let mut source = node.clone();
676            for row in &mut source.asm_ops {
677                row.context_name_idx = name;
678            }
679            let source = builder.add_node(source).unwrap();
680            let mut function = template;
681            function.source_node = Some(source).into();
682            function.name_idx = name;
683            function.linkage_name_idx = Some(name).into();
684            builder.add_function(function);
685        }
686        let info = Arc::<PackageDebugInfo>::from(builder.build());
687        assert!(info.to_bytes().len() < 16 * 1024 * 1024);
688        let index = FunctionIndex::new(info.clone(), package.mast_forest().clone());
689        assert_eq!(index.entry_inline_counts.len(), 50_001);
690        assert_eq!(index.ranges[&node.exec_node].len(), 50_001);
691        for position in [1, 25_000, 50_000] {
692            let function = DebugFunctionIdx::from(position);
693            let source = info[function].source_node.into_option().unwrap();
694            assert_eq!(index.source_function(source), Some(function));
695            assert_eq!(index.entry_function(source), Some(function));
696        }
697    }
698}