miden_assembly/linker/
mod.rs

1mod analysis;
2mod callgraph;
3mod debug;
4mod errors;
5mod name_resolver;
6mod rewrites;
7
8use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec};
9use core::ops::Index;
10
11use miden_core::{Kernel, Word};
12use smallvec::{SmallVec, smallvec};
13
14use self::{analysis::MaybeRewriteCheck, rewrites::ModuleRewriter};
15pub use self::{
16    callgraph::{CallGraph, CycleError},
17    errors::LinkerError,
18    name_resolver::{CallerInfo, NameResolver, ResolvedTarget},
19};
20use super::{GlobalProcedureIndex, ModuleIndex};
21use crate::{
22    Library, LibraryNamespace, LibraryPath, SourceManager, Spanned,
23    ast::{
24        Export, InvocationTarget, InvokeKind, Module, ProcedureIndex, ProcedureName,
25        ResolvedProcedure,
26    },
27    library::{ModuleInfo, ProcedureInfo},
28};
29
30// LINKER INPUTS
31// ================================================================================================
32
33/// Represents a linked procedure in the procedure graph of the [`Linker`]
34pub enum ProcedureLink<'a> {
35    /// A procedure which we have the original AST for, and may require additional processing
36    Ast(&'a Export),
37    /// A procedure which we have the MAST for, no additional processing required
38    Info(&'a ProcedureInfo),
39}
40
41impl ProcedureLink<'_> {
42    /// Returns the name of the procedure.
43    pub fn name(&self) -> &ProcedureName {
44        match self {
45            Self::Ast(p) => p.name(),
46            Self::Info(p) => &p.name,
47        }
48    }
49
50    /// Returns the wrapped procedure if in the `Ast` representation, or panics otherwise.
51    ///
52    /// # Panics
53    /// - Panics if the wrapped procedure is not in the `Ast` representation.
54    pub fn unwrap_ast(&self) -> &Export {
55        match self {
56            Self::Ast(proc) => proc,
57            Self::Info(_) => panic!("expected AST procedure, but was compiled"),
58        }
59    }
60
61    /// Returns true if the wrapped procedure is in the `Ast` representation.
62    pub fn is_ast(&self) -> bool {
63        matches!(self, Self::Ast(_))
64    }
65}
66
67/// Represents a linked module in the module graph of the [`Linker`]
68#[derive(Clone)]
69pub enum ModuleLink {
70    /// A module which we have the original AST for, and may require additional processing
71    Ast(Arc<Module>),
72    /// A previously-assembled module we have MAST for, no additional processing required
73    Info(ModuleInfo),
74}
75
76impl ModuleLink {
77    /// Returns the library path of the wrapped module.
78    pub fn path(&self) -> &LibraryPath {
79        match self {
80            Self::Ast(m) => m.path(),
81            Self::Info(m) => m.path(),
82        }
83    }
84
85    /// Returns the wrapped module if in the `Ast` representation, or panics otherwise.
86    ///
87    /// # Panics
88    /// - Panics if the wrapped module is not in the `Ast` representation.
89    pub fn unwrap_ast(&self) -> &Arc<Module> {
90        match self {
91            Self::Ast(module) => module,
92            Self::Info(_) => {
93                panic!("expected module to be in AST representation, but was compiled")
94            },
95        }
96    }
97
98    /// Resolves `name` to a procedure within the local scope of this module.
99    pub fn resolve(&self, name: &ProcedureName) -> Option<ResolvedProcedure> {
100        match self {
101            ModuleLink::Ast(module) => module.resolve(name),
102            ModuleLink::Info(module) => {
103                module.get_procedure_digest_by_name(name).map(ResolvedProcedure::MastRoot)
104            },
105        }
106    }
107}
108
109/// Represents an AST module which has not been linked yet
110#[derive(Clone)]
111pub struct PreLinkModule {
112    pub module: Box<Module>,
113    pub module_index: ModuleIndex,
114}
115
116/// Represents an assembled module or modules to use when resolving references while linking,
117/// as well as the method by which referenced symbols will be linked into the assembled MAST.
118#[derive(Clone)]
119pub struct LinkLibrary {
120    /// The library to link
121    pub library: Arc<Library>,
122    /// How to link against this library
123    pub kind: LinkLibraryKind,
124}
125
126impl LinkLibrary {
127    /// Dynamically link against `library`
128    pub fn dynamic(library: Arc<Library>) -> Self {
129        Self { library, kind: LinkLibraryKind::Dynamic }
130    }
131
132    /// Statically link `library`
133    pub fn r#static(library: Arc<Library>) -> Self {
134        Self { library, kind: LinkLibraryKind::Static }
135    }
136}
137
138/// Represents how a library should be linked into the assembled MAST
139#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
140pub enum LinkLibraryKind {
141    /// A dynamically-linked library.
142    ///
143    /// References to symbols of dynamically-linked libraries expect to have those symbols resolved
144    /// at runtime, i.e. it is expected that the library was loaded (or will be loaded on-demand),
145    /// and that the referenced symbol is resolvable by the VM.
146    ///
147    /// Concretely, the digest corresponding to a referenced procedure symbol will be linked as a
148    /// [`miden_core::mast::ExternalNode`], rather than including the procedure in the assembled
149    /// MAST, and referencing the procedure via [`miden_core::mast::MastNodeId`].
150    #[default]
151    Dynamic,
152    /// A statically-linked library.
153    ///
154    /// References to symbols of statically-linked libraries expect to be resolvable by the linker,
155    /// during assembly, i.e. it is expected that the library was provided to the assembler/linker
156    /// as an input, and that the entire definition of the referenced symbol is available.
157    ///
158    /// Concretely, a statically linked procedure will have its root, and all reachable nodes found
159    /// in the MAST of the library, included in the assembled MAST, and referenced via
160    /// [`miden_core::mast::MastNodeId`].
161    ///
162    /// Statically linked symbols are thus merged into the assembled artifact as if they had been
163    /// defined in your own project, and the library they were originally defined in will not be
164    /// required to be provided at runtime, as is the case with dynamically-linked libraries.
165    Static,
166}
167
168// LINKER
169// ================================================================================================
170
171/// The [`Linker`] is responsible for analyzing the input modules and libraries provided to the
172/// assembler, and _linking_ them together.
173///
174/// The core conceptual data structure of the linker is the _module graph_, which is implemented
175/// by a vector of module nodes, and a _call graph_, which is implemented as an adjacency matrix
176/// of procedure nodes and the outgoing edges from those nodes, representing references from that
177/// procedure to another symbol (typically as the result of procedure invocation, hence "call"
178/// graph).
179///
180/// Each procedure known to the linker is given a _global procedure index_, which is actually a
181/// pair of indices: a _module index_ (which indexes into the vector of module nodes), and a
182/// _procedure index_ (which indexes into the set of procedures defined by a module). These global
183/// procedure indices function as a unique identifier within the linker, to a specific procedure,
184/// and can be resolved to either the procedure AST, or to metadata about the procedure MAST.
185///
186/// The process of linking involves two phases:
187///
188/// 1. Setting up the linker context, by providing the set of libraries and/or input modules to link
189/// 2. Analyzing and rewriting the module graph, as needed, to ensure that all procedure references
190///    are resolved to either a concrete definition, or a "phantom" reference in the form of a MAST
191///    root.
192///
193/// The assembler will call [`Self::link`] once it has provided all inputs that it wants to link,
194/// which will, when successful, return the set of module indices corresponding to the modules that
195/// comprise the public interface of the assembled artifact. The assembler then constructs the MAST
196/// starting from the exported procedures of those modules, recursively tracing the call graph
197/// based on whether or not the callee is statically or dynamically linked. In the static linking
198/// case, any procedures referenced in a statically-linked library or module will be included in
199/// the assembled artifact. In the dynamic linking case, referenced procedures are instead
200/// referenced in the assembled artifact only by their MAST root.
201#[derive(Clone)]
202pub struct Linker {
203    /// The set of libraries to link against.
204    libraries: BTreeMap<Word, LinkLibrary>,
205    /// The nodes of the module graph data structure maintained by the linker.
206    modules: Vec<Option<ModuleLink>>,
207    /// The set of modules pending additional processing before adding them to the graph.
208    ///
209    /// When adding a set of inter-dependent modules to the graph, we process them as a group, so
210    /// that any references between them can be resolved, and the contents of the module
211    /// rewritten to reflect the changes.
212    ///
213    /// Once added to the graph, modules become immutable, and any additional modules added after
214    /// that must by definition only depend on modules in the graph, and not be depended upon.
215    pending: Vec<PreLinkModule>,
216    /// The global call graph of calls, not counting those that are performed directly via MAST
217    /// root.
218    callgraph: CallGraph,
219    /// The set of MAST roots which have procedure definitions in this graph. There can be
220    /// multiple procedures bound to the same root due to having identical code.
221    procedures_by_mast_root: BTreeMap<Word, SmallVec<[GlobalProcedureIndex; 1]>>,
222    /// The index of the kernel module in `modules`, if present
223    kernel_index: Option<ModuleIndex>,
224    /// The kernel library being linked against.
225    ///
226    /// This is always provided, with an empty kernel being the default.
227    kernel: Kernel,
228    /// The source manager to use when emitting diagnostics.
229    source_manager: Arc<dyn SourceManager>,
230}
231
232// ------------------------------------------------------------------------------------------------
233/// Constructors
234impl Linker {
235    /// Instantiate a new [Linker], using the provided [SourceManager] to resolve source info.
236    pub fn new(source_manager: Arc<dyn SourceManager>) -> Self {
237        Self {
238            libraries: Default::default(),
239            modules: Default::default(),
240            pending: Default::default(),
241            callgraph: Default::default(),
242            procedures_by_mast_root: Default::default(),
243            kernel_index: None,
244            kernel: Default::default(),
245            source_manager,
246        }
247    }
248
249    /// Registers `library` and all of its modules with the linker, according to its kind
250    pub fn link_library(&mut self, library: LinkLibrary) -> Result<(), LinkerError> {
251        use alloc::collections::btree_map::Entry;
252
253        match self.libraries.entry(*library.library.digest()) {
254            Entry::Vacant(entry) => {
255                entry.insert(library.clone());
256                self.link_assembled_modules(library.library.module_infos())
257            },
258            Entry::Occupied(mut entry) => {
259                let prev = entry.get_mut();
260
261                // If the same library is linked both dynamically and statically, prefer static
262                // linking always.
263                if matches!(prev.kind, LinkLibraryKind::Dynamic) {
264                    prev.kind = library.kind;
265                }
266
267                Ok(())
268            },
269        }
270    }
271
272    /// Registers a set of MAST modules with the linker.
273    ///
274    /// If called directly, the modules will default to being dynamically linked. You must use
275    /// [`Self::link_library`] if you wish to statically link a set of assembled modules.
276    pub fn link_assembled_modules(
277        &mut self,
278        modules: impl IntoIterator<Item = ModuleInfo>,
279    ) -> Result<(), LinkerError> {
280        for module in modules {
281            self.link_assembled_module(module)?;
282        }
283
284        Ok(())
285    }
286
287    /// Registers a MAST module with the linker.
288    ///
289    /// If called directly, the module will default to being dynamically linked. You must use
290    /// [`Self::link_library`] if you wish to statically link `module`.
291    pub fn link_assembled_module(
292        &mut self,
293        module: ModuleInfo,
294    ) -> Result<ModuleIndex, LinkerError> {
295        log::debug!(target: "linker", "adding pre-assembled module {} to module graph", module.path());
296
297        let module_path = module.path();
298        let is_duplicate =
299            self.is_pending(module_path) || self.find_module_index(module_path).is_some();
300        if is_duplicate {
301            return Err(LinkerError::DuplicateModule { path: module_path.clone() });
302        }
303
304        let module_index = self.next_module_id();
305        for (proc_index, proc) in module.procedures() {
306            let gid = module_index + proc_index;
307            self.register_procedure_root(gid, proc.digest)?;
308            self.callgraph.get_or_insert_node(gid);
309        }
310        self.modules.push(Some(ModuleLink::Info(module)));
311        Ok(module_index)
312    }
313
314    /// Registers a set of AST modules with the linker.
315    ///
316    /// See [`Self::link_module`] for more details.
317    pub fn link_modules(
318        &mut self,
319        modules: impl IntoIterator<Item = Box<Module>>,
320    ) -> Result<Vec<ModuleIndex>, LinkerError> {
321        modules.into_iter().map(|m| self.link_module(m)).collect()
322    }
323
324    /// Registers an AST module with the linker.
325    ///
326    /// A module provided to this method is presumed to be dynamically linked, unless specifically
327    /// handled otherwise by the assembler. In particular, the assembler will only statically link
328    /// the set of AST modules provided to [`Self::link`], as they are expected to comprise the
329    /// public interface of the assembled artifact.
330    ///
331    /// # Errors
332    ///
333    /// This operation can fail for the following reasons:
334    ///
335    /// * Module with same [LibraryPath] is in the graph already
336    /// * Too many modules in the graph
337    ///
338    /// # Panics
339    ///
340    /// This function will panic if the number of modules exceeds the maximum representable
341    /// [ModuleIndex] value, `u16::MAX`.
342    pub fn link_module(&mut self, module: Box<Module>) -> Result<ModuleIndex, LinkerError> {
343        log::debug!(target: "linker", "adding unprocessed module {}", module.path());
344        let module_path = module.path();
345
346        let is_duplicate =
347            self.is_pending(module_path) || self.find_module_index(module_path).is_some();
348        if is_duplicate {
349            return Err(LinkerError::DuplicateModule { path: module_path.clone() });
350        }
351
352        let module_index = self.next_module_id();
353        self.modules.push(None);
354        self.pending.push(PreLinkModule { module, module_index });
355        Ok(module_index)
356    }
357
358    fn is_pending(&self, path: &LibraryPath) -> bool {
359        self.pending.iter().any(|m| m.module.path() == path)
360    }
361
362    #[inline]
363    fn next_module_id(&self) -> ModuleIndex {
364        ModuleIndex::new(self.modules.len())
365    }
366}
367
368// ------------------------------------------------------------------------------------------------
369/// Kernels
370impl Linker {
371    /// Returns a new [Linker] instantiated from the provided kernel and kernel info module.
372    ///
373    /// Note: it is assumed that kernel and kernel_module are consistent, but this is not checked.
374    ///
375    /// TODO: consider passing `KerneLibrary` into this constructor as a parameter instead.
376    pub(super) fn with_kernel(
377        source_manager: Arc<dyn SourceManager>,
378        kernel: Kernel,
379        kernel_module: ModuleInfo,
380    ) -> Self {
381        assert!(!kernel.is_empty());
382        assert_eq!(kernel_module.path(), &LibraryPath::from(LibraryNamespace::Kernel));
383        log::debug!(target: "linker", "instantiating linker with kernel {}", kernel_module.path());
384
385        let mut graph = Self::new(source_manager);
386        let kernel_index = graph
387            .link_assembled_module(kernel_module)
388            .expect("failed to add kernel module to the module graph");
389
390        graph.kernel_index = Some(kernel_index);
391        graph.kernel = kernel;
392        graph
393    }
394
395    pub fn kernel(&self) -> &Kernel {
396        &self.kernel
397    }
398
399    pub fn has_nonempty_kernel(&self) -> bool {
400        self.kernel_index.is_some() || !self.kernel.is_empty()
401    }
402}
403
404// ------------------------------------------------------------------------------------------------
405/// Analysis
406impl Linker {
407    /// Links `modules` using the current state of the linker.
408    ///
409    /// Returns the module indices corresponding to the provided modules, which are expected to
410    /// provide the public interface of the final assembled artifact.
411    pub fn link(
412        &mut self,
413        modules: impl IntoIterator<Item = Box<Module>>,
414    ) -> Result<Vec<ModuleIndex>, LinkerError> {
415        let module_indices = self.link_modules(modules)?;
416
417        self.link_and_rewrite()?;
418
419        Ok(module_indices)
420    }
421
422    /// Links `kernel` using the current state of the linker.
423    ///
424    /// Returns the module index of the kernel module, which is expected to provide the public
425    /// interface of the final assembled kernel.
426    ///
427    /// This differs from `link` in that we allow all AST modules in the module graph access to
428    /// kernel features, e.g. `caller`, as if they are defined by the kernel module itself.
429    pub fn link_kernel(&mut self, kernel: Box<Module>) -> Result<Vec<ModuleIndex>, LinkerError> {
430        let module_index = self.link_module(kernel)?;
431
432        // Set the module kind of all pending AST modules to Kernel, as we are linking a kernel
433        for module in self.pending.iter_mut() {
434            module.module.set_kind(crate::ast::ModuleKind::Kernel);
435        }
436
437        self.link_and_rewrite()?;
438
439        Ok(vec![module_index])
440    }
441
442    /// Compute the module graph from the set of pending modules, and link it, rewriting any AST
443    /// modules with unresolved, or partially-resolved, symbol references.
444    ///
445    /// This should be called any time you add more libraries or modules to the module graph, to
446    /// ensure that the graph is valid, and that there are no unresolved references. In general,
447    /// you will only instantiate the linker, build up the graph, and link a single time; but you
448    /// can re-use the linker to build multiple artifacts as well.
449    ///
450    /// When this function is called, some initial information is calculated about the AST modules
451    /// which are to be added to the graph, and then each module is visited to perform a deeper
452    /// analysis than can be done by the `sema` module, as we now have the full set of modules
453    /// available to do import resolution, and to rewrite invoke targets with their absolute paths
454    /// and/or MAST roots. A variety of issues are caught at this stage.
455    ///
456    /// Once each module is validated, the various analysis results stored as part of the graph
457    /// structure are updated to reflect that module being added to the graph. Once part of the
458    /// graph, the module becomes immutable/clone-on-write, so as to allow the graph to be
459    /// cheaply cloned.
460    ///
461    /// The final, and most important, analysis done by this function is the topological sort of
462    /// the global call graph, which contains the inter-procedural dependencies of every procedure
463    /// in the module graph. We use this sort order to do two things:
464    ///
465    /// 1. Verify that there are no static cycles in the graph that would prevent us from being able
466    ///    to hash the generated MAST of the program. NOTE: dynamic cycles, e.g. those induced by
467    ///    `dynexec`, are perfectly fine, we are only interested in preventing cycles that interfere
468    ///    with the ability to generate MAST roots.
469    ///
470    /// 2. Visit the call graph bottom-up, so that we can fully compile a procedure before any of
471    ///    its callers, and thus rewrite those callers to reference that procedure by MAST root,
472    ///    rather than by name. As a result, a compiled MAST program is like an immutable snapshot
473    ///    of the entire call graph at the time of compilation. Later, if we choose to recompile a
474    ///    subset of modules (currently we do not have support for this in the assembler API), we
475    ///    can re-analyze/re-compile only those parts of the graph which have actually changed.
476    ///
477    /// NOTE: This will return `Err` if we detect a validation error, a cycle in the graph, or an
478    /// operation not supported by the current configuration. Basically, for any reason that would
479    /// cause the resulting graph to represent an invalid program.
480    fn link_and_rewrite(&mut self) -> Result<(), LinkerError> {
481        log::debug!(target: "linker", "processing {} new modules, and recomputing module graph", self.pending.len());
482
483        // It is acceptable for there to be no changes, but if the graph is empty and no changes
484        // are being made, we treat that as an error
485        if self.modules.is_empty() && self.pending.is_empty() {
486            return Err(LinkerError::Empty);
487        }
488
489        // If no changes are being made, we're done
490        if self.pending.is_empty() {
491            return Ok(());
492        }
493
494        // Visit all of the pending modules, assigning them ids, and adding them to the module
495        // graph after rewriting any calls to use absolute paths
496        let high_water_mark = self.modules.len();
497        let pending = core::mem::take(&mut self.pending);
498        for PreLinkModule { module: pending_module, module_index } in pending.iter() {
499            log::debug!(
500                target: "linker",
501                "adding procedures from pending module {} (index {}) to call graph",
502                pending_module.path(),
503                module_index.as_usize()
504            );
505
506            // Apply module to call graph
507            for (index, _) in pending_module.procedures().enumerate() {
508                let procedure_id = ProcedureIndex::new(index);
509                let global_id = GlobalProcedureIndex {
510                    module: *module_index,
511                    index: procedure_id,
512                };
513
514                // Ensure all entrypoints and exported symbols are represented in the call
515                // graph, even if they have no edges, we need them
516                // in the graph for the topological sort
517                self.callgraph.get_or_insert_node(global_id);
518            }
519        }
520
521        // Obtain a set of resolvers for the pending modules so that we can do name resolution
522        // before they are added to the graph
523        let mut resolver = NameResolver::new(self);
524        for module in pending.iter() {
525            resolver.push_pending(module);
526        }
527        let mut edges = Vec::new();
528        let mut finished: Vec<PreLinkModule> = Vec::with_capacity(pending.len());
529
530        // Visit all of the newly-added modules and perform any rewrites to AST modules.
531        for PreLinkModule { mut module, module_index } in pending.into_iter() {
532            log::debug!(target: "linker", "rewriting pending module {} (index {})", module.path(), module_index.as_usize());
533
534            let mut rewriter = ModuleRewriter::new(&resolver);
535            rewriter.apply(module_index, &mut module)?;
536
537            log::debug!(
538                target: "linker",
539                "processing procedures of pending module {} (index {})",
540                module.path(),
541                module_index.as_usize()
542            );
543            for (index, procedure) in module.procedures().enumerate() {
544                log::debug!(target: "linker", "  * processing {} at index {index}", procedure.name());
545
546                let procedure_id = ProcedureIndex::new(index);
547                let gid = GlobalProcedureIndex {
548                    module: module_index,
549                    index: procedure_id,
550                };
551
552                // Add edge to the call graph to represent dependency on aliased procedures
553                if let Export::Alias(alias) = procedure {
554                    log::debug!(target: "linker", "  | resolving alias {}..", alias.target());
555
556                    let caller = CallerInfo {
557                        span: alias.span(),
558                        module: module_index,
559                        kind: InvokeKind::ProcRef,
560                    };
561                    let target = alias.target().into();
562                    if let Some(callee) =
563                        resolver.resolve_target(&caller, &target)?.into_global_id()
564                    {
565                        log::debug!(
566                            target: "linker",
567                            "  | resolved alias to gid {:?}:{:?}",
568                            callee.module,
569                            callee.index
570                        );
571                        edges.push((gid, callee));
572                    }
573                }
574
575                // Add edges to all transitive dependencies of this procedure due to calls
576                for invoke in procedure.invoked() {
577                    log::debug!(target: "linker", "  | recording {} dependency on {}", invoke.kind, &invoke.target);
578
579                    let caller = CallerInfo {
580                        span: invoke.span(),
581                        module: module_index,
582                        kind: invoke.kind,
583                    };
584                    if let Some(callee) =
585                        resolver.resolve_target(&caller, &invoke.target)?.into_global_id()
586                    {
587                        log::debug!(
588                            target: "linker",
589                            "  | resolved dependency to gid {}:{}",
590                            callee.module.as_usize(),
591                            callee.index.as_usize()
592                        );
593                        edges.push((gid, callee));
594                    }
595                }
596            }
597
598            finished.push(PreLinkModule { module, module_index });
599        }
600
601        // Release the graph again
602        drop(resolver);
603
604        // Update the graph with the processed modules
605        for PreLinkModule { module, module_index } in finished {
606            self.modules[module_index.as_usize()] = Some(ModuleLink::Ast(Arc::from(module)));
607        }
608
609        edges
610            .into_iter()
611            .for_each(|(caller, callee)| self.callgraph.add_edge(caller, callee));
612
613        // Visit all of the (AST) modules in the base module graph, and modify them if any of the
614        // pending modules allow additional information to be inferred (such as the absolute path of
615        // imports, etc)
616        for module_index in 0..high_water_mark {
617            let module_index = ModuleIndex::new(module_index);
618            let module = self.modules[module_index.as_usize()].clone().unwrap_or_else(|| {
619                panic!(
620                    "expected module at index {} to have been processed, but it is None",
621                    module_index.as_usize()
622                )
623            });
624
625            match module {
626                ModuleLink::Ast(module) => {
627                    log::debug!(target: "linker", "re-analyzing module {} (index {})", module.path(), module_index.as_usize());
628                    // Re-analyze the module, and if we needed to clone-on-write, the new module
629                    // will be returned. Otherwise, `Ok(None)` indicates that
630                    // the module is unchanged, and `Err` indicates that
631                    // re-analysis has found an issue with this module.
632                    let new_module =
633                        self.reanalyze_module(module_index, module).map(ModuleLink::Ast)?;
634                    self.modules[module_index.as_usize()] = Some(new_module);
635                },
636                module => {
637                    self.modules[module_index.as_usize()] = Some(module);
638                },
639            }
640        }
641
642        // Make sure the graph is free of cycles
643        self.callgraph.toposort().map_err(|cycle| {
644            let iter = cycle.into_node_ids();
645            let mut nodes = Vec::with_capacity(iter.len());
646            for node in iter {
647                let module = self[node.module].path();
648                let proc = self.get_procedure_unsafe(node);
649                nodes.push(format!("{}::{}", module, proc.name()));
650            }
651            LinkerError::Cycle { nodes: nodes.into() }
652        })?;
653
654        Ok(())
655    }
656
657    fn reanalyze_module(
658        &mut self,
659        module_id: ModuleIndex,
660        module: Arc<Module>,
661    ) -> Result<Arc<Module>, LinkerError> {
662        let resolver = NameResolver::new(self);
663        let maybe_rewrite = MaybeRewriteCheck::new(&resolver);
664        if maybe_rewrite.check(module_id, &module)? {
665            // We need to rewrite this module again, so get an owned copy of the original
666            // and use that
667            let mut module = Box::new(Arc::unwrap_or_clone(module));
668            let mut rewriter = ModuleRewriter::new(&resolver);
669            rewriter.apply(module_id, &mut module)?;
670
671            Ok(Arc::from(module))
672        } else {
673            Ok(module)
674        }
675    }
676}
677
678// ------------------------------------------------------------------------------------------------
679/// Accessors/Queries
680impl Linker {
681    /// Get an iterator over the external libraries the linker has linked against
682    pub fn libraries(&self) -> impl Iterator<Item = &LinkLibrary> {
683        self.libraries.values()
684    }
685
686    /// Compute the topological sort of the callgraph rooted at `caller`
687    pub fn topological_sort_from_root(
688        &self,
689        caller: GlobalProcedureIndex,
690    ) -> Result<Vec<GlobalProcedureIndex>, CycleError> {
691        self.callgraph.toposort_caller(caller)
692    }
693
694    /// Fetch a [ProcedureLink] by its [GlobalProcedureIndex].
695    ///
696    /// # Panics
697    /// - Panics if index is invalid.
698    pub fn get_procedure_unsafe(&self, id: GlobalProcedureIndex) -> ProcedureLink<'_> {
699        match self.modules[id.module.as_usize()]
700            .as_ref()
701            .expect("invalid reference to pending module")
702        {
703            ModuleLink::Ast(m) => ProcedureLink::Ast(&m[id.index]),
704            ModuleLink::Info(m) => ProcedureLink::Info(m.get_procedure_by_index(id.index).unwrap()),
705        }
706    }
707
708    /// Returns a procedure index which corresponds to the provided procedure digest.
709    ///
710    /// Note that there can be many procedures with the same digest - due to having the same code,
711    /// and/or using different decorators which don't affect the MAST root. This method returns an
712    /// arbitrary one.
713    pub fn get_procedure_index_by_digest(
714        &self,
715        procedure_digest: &Word,
716    ) -> Option<GlobalProcedureIndex> {
717        self.procedures_by_mast_root.get(procedure_digest).map(|indices| indices[0])
718    }
719
720    /// Resolves `target` from the perspective of `caller`.
721    pub fn resolve_target(
722        &self,
723        caller: &CallerInfo,
724        target: &InvocationTarget,
725    ) -> Result<ResolvedTarget, LinkerError> {
726        let resolver = NameResolver::new(self);
727        resolver.resolve_target(caller, target)
728    }
729
730    /// Registers a [MastNodeId] as corresponding to a given [GlobalProcedureIndex].
731    ///
732    /// # SAFETY
733    ///
734    /// It is essential that the caller _guarantee_ that the given digest belongs to the specified
735    /// procedure. It is fine if there are multiple procedures with the same digest, but it _must_
736    /// be the case that if a given digest is specified, it can be used as if it was the definition
737    /// of the referenced procedure, i.e. they are referentially transparent.
738    pub(crate) fn register_procedure_root(
739        &mut self,
740        id: GlobalProcedureIndex,
741        procedure_mast_root: Word,
742    ) -> Result<(), LinkerError> {
743        use alloc::collections::btree_map::Entry;
744        match self.procedures_by_mast_root.entry(procedure_mast_root) {
745            Entry::Occupied(ref mut entry) => {
746                let prev_id = entry.get()[0];
747                if prev_id != id {
748                    // Multiple procedures with the same root, but compatible
749                    entry.get_mut().push(id);
750                }
751            },
752            Entry::Vacant(entry) => {
753                entry.insert(smallvec![id]);
754            },
755        }
756
757        Ok(())
758    }
759
760    /// Resolve a [LibraryPath] to a [ModuleIndex] in this graph
761    pub fn find_module_index(&self, name: &LibraryPath) -> Option<ModuleIndex> {
762        self.modules
763            .iter()
764            .position(|m| m.as_ref().is_some_and(|m| m.path() == name))
765            .map(ModuleIndex::new)
766    }
767
768    /// Resolve a [LibraryPath] to a [Module] in this graph
769    pub fn find_module(&self, name: &LibraryPath) -> Option<ModuleLink> {
770        self.modules
771            .iter()
772            .find(|m| m.as_ref().is_some_and(|m| m.path() == name))
773            .cloned()
774            .unwrap_or(None)
775    }
776}
777
778impl Index<ModuleIndex> for Linker {
779    type Output = ModuleLink;
780
781    fn index(&self, index: ModuleIndex) -> &Self::Output {
782        self.modules
783            .index(index.as_usize())
784            .as_ref()
785            .expect("invalid reference to pending module")
786    }
787}