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