Skip to main content

miden_assembly/linker/
mod.rs

1//! Assembly of a Miden Assembly project is comprised of four phases:
2//!
3//! 1. _Parsing_, where MASM sources are parsed into the AST data structure. Some light validation
4//!    is done in this phase, to catch invalid syntax, invalid immediate values (e.g. overflow), and
5//!    other simple checks that require little to no reasoning about surrounding context.
6//! 2. _Semantic analysis_, where initial validation of the AST is performed. This step catches
7//!    unused imports, references to undefined local symbols, orphaned doc comments, and other
8//!    checks that only require minimal module-local context. Initial symbol resolution is performed
9//!    here based on module-local context, as well as constant folding of expressions that can be
10//!    resolved locally. Symbols which refer to external items are unable to be fully processed as
11//!    part of this phase, and is instead left to the linking phase.
12//! 3. _Linking_, the most critical phase of compilation. During this phase, the assembler has the
13//!    full compilation graph available to it, and so this is where inter-module symbol references
14//!    are finally able to be resolved (or not, in which case appropriate errors are raised). This
15//!    is the phase where we catch cyclic references, references to undefined symbols, references to
16//!    non-public symbols from other modules, etc. Once all symbols are linked, the assembler is
17//!    free to compile all of the procedures to MAST, and generate a [crate::package::Package].
18//! 4. _Assembly_, the final phase, where all of the linked items provided to the assembler are
19//!    lowered to MAST, or to their final representations in the [crate::package::Package] produced
20//!    as the output of assembly. During this phase, it is expected that the compilation graph has
21//!    been validated by the linker, and we're simply processing the conversion to MAST.
22//!
23//! This module provides the implementation of the linker and its associated data structures. There
24//! are three primary parts:
25//!
26//! 1. The _call graph_, this is what tracks dependencies between procedures in the compilation
27//!    graph, and is used to ensure that all procedure references can be resolved to a MAST root
28//!    during final assembly.
29//! 2. The _symbol resolver_, this is what is responsible for computing symbol resolutions using
30//!    context-sensitive details about how a symbol is referenced. This context sensitivity is how
31//!    we are able to provide better diagnostics when invalid references are found. The resolver
32//!    shares part of it's implementation with the same infrastructure used for symbol resolution
33//!    that is performed during semantic analysis - the difference is that at link-time, we are
34//!    stricter about what happens when a symbol cannot be resolved correctly.
35//! 3. A set of _rewrites_, applied to symbols/modules at link-time, which rewrite the AST so that
36//!    all symbol references and constant expressions are fully resolved/folded. This is where any
37//!    final issues are discovered, and the AST is prepared for lowering to MAST.
38mod callgraph;
39mod debug;
40mod errors;
41mod library;
42mod module;
43pub mod namespaces;
44mod resolver;
45mod rewrites;
46mod symbols;
47
48use alloc::{boxed::Box, collections::BTreeMap, string::ToString, sync::Arc, vec::Vec};
49use core::{
50    cell::RefCell,
51    ops::{ControlFlow, Index},
52};
53
54use miden_assembly_syntax::{
55    Report,
56    ast::{
57        self, AttributeSet, GlobalItemIndex, InvocationTarget, ItemIndex, Module, ModuleIndex,
58        Path, SymbolResolution, Visibility, types,
59    },
60    debuginfo::{SourceManager, SourceSpan, Span, Spanned},
61    module::{ItemInfo, ModuleInfo},
62};
63use miden_core::{Word, advice::AdviceMap, mast::MastNodeId, program::Kernel};
64use miden_mast_package::Package as MastPackage;
65use smallvec::{SmallVec, smallvec};
66
67pub use self::{
68    callgraph::{CallGraph, CycleError},
69    errors::LinkerError,
70    library::{LinkLibrary, Linkage},
71    namespaces::NamespaceGraph,
72    resolver::{ResolverCache, SymbolResolutionContext, SymbolResolver},
73    symbols::{Import, Symbol, SymbolItem},
74};
75use self::{
76    module::{LinkModule, ModuleSource},
77    namespaces::ResolvedImports,
78    resolver::*,
79};
80
81/// Represents the current status of a symbol in the state of the [Linker]
82#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
83pub enum LinkStatus {
84    /// The module or item has not been visited by the linker
85    #[default]
86    Unlinked,
87    /// The module or item has been visited by the linker, but still refers to one or more
88    /// unresolved symbols.
89    PartiallyLinked,
90    /// The module or item has been visited by the linker, and is fully linked and resolved
91    Linked,
92}
93
94// LINKER
95// ================================================================================================
96
97/// The [`Linker`] is responsible for analyzing the input modules and libraries provided to the
98/// assembler, and _linking_ them together.
99///
100/// The core conceptual data structure of the linker is the _module graph_, which is implemented
101/// by a vector of module nodes, and a _call graph_, which is implemented as an adjacency matrix
102/// of item nodes and the outgoing edges from those nodes, representing references from that item
103/// to another symbol (typically as the result of procedure invocation, hence "call" graph).
104///
105/// Each item/symbol known to the linker is given a _global item index_, which is actually a pair
106/// of indices: a _module index_ (which indexes into the vector of module nodes), and an _item
107/// index_ (which indexes into the items defined by a module). These global item indices function
108/// as a unique identifier within the linker, to a specific item, and can be resolved to either the
109/// original syntax tree of the item, or to metadata about the item retrieved from previously-
110/// assembled MAST.
111///
112/// The process of linking involves two phases:
113///
114/// 1. Setting up the linker context, by providing the set of inputs to link together
115/// 2. Analyzing and rewriting the symbols known to the linker, as needed, to ensure that all symbol
116///    references are resolved to concrete definitions.
117///
118/// The assembler will call [`Self::link`] once it has provided all inputs that it wants to link,
119/// which will, when successful, return the set of module indices corresponding to the modules that
120/// comprise the public interface of the assembled artifact. The assembler then constructs the MAST
121/// starting from the exported procedures of those modules, recursively tracing the call graph
122/// based on whether or not the callee is statically or dynamically linked. In the static linking
123/// case, any procedures referenced in a statically-linked library or module will be included in
124/// the assembled artifact. In the dynamic linking case, referenced procedures are instead
125/// referenced in the assembled artifact only by their MAST root.
126#[derive(Clone)]
127pub struct Linker {
128    /// The set of libraries to link against.
129    libraries: BTreeMap<Word, LinkLibrary>,
130    /// The statically linked libraries to pass to MAST forest construction.
131    ///
132    /// This index is keyed by full MAST forest commitment, not package digest, so static libraries
133    /// with the same exported procedure roots but different stored advice are retained.
134    static_libraries: BTreeMap<Word, LinkLibrary>,
135    /// The global set of items known to the linker
136    modules: Vec<LinkModule>,
137    /// The global call graph of calls, not counting those that are performed directly via MAST
138    /// root.
139    callgraph: CallGraph,
140    /// The set of MAST roots which have procedure definitions in this graph. There can be
141    /// multiple procedures bound to the same root due to having identical code.
142    procedures_by_mast_root: BTreeMap<Word, SmallVec<[GlobalItemIndex; 1]>>,
143    /// The index of the kernel module in `modules`, if present
144    kernel_index: Option<ModuleIndex>,
145    /// The kernel library being linked against.
146    ///
147    /// This is always provided, with an empty kernel being the default.
148    kernel: Kernel,
149    kernel_package: Option<Arc<MastPackage>>,
150    /// The source manager to use when emitting diagnostics.
151    source_manager: Arc<dyn SourceManager>,
152}
153
154// ------------------------------------------------------------------------------------------------
155/// Constructors
156impl Linker {
157    /// Instantiate a new [Linker], using the provided [SourceManager] to resolve source info.
158    pub fn new(source_manager: Arc<dyn SourceManager>) -> Self {
159        Self {
160            libraries: Default::default(),
161            static_libraries: Default::default(),
162            modules: Default::default(),
163            callgraph: Default::default(),
164            procedures_by_mast_root: Default::default(),
165            kernel_index: None,
166            kernel: Default::default(),
167            kernel_package: None,
168            source_manager,
169        }
170    }
171
172    /// Registers `library` and all of its modules with the linker, according to its linkage
173    pub fn link_library(&mut self, library: LinkLibrary) -> Result<(), LinkerError> {
174        use alloc::collections::btree_map::Entry;
175
176        let module_infos =
177            library.module_infos().map_err(|err| LinkerError::InvalidPackageModuleSurface {
178                package: library.package.name.to_string(),
179                reason: err.to_string(),
180            })?;
181        let library_interface_digest = library.package.interface_digest().map_err(|err| {
182            LinkerError::InvalidPackageModuleSurface {
183                package: library.package.name.to_string(),
184                reason: err.to_string(),
185            }
186        })?;
187
188        let static_library = matches!(library.linkage, Linkage::Static).then(|| library.clone());
189        let result = match self.libraries.entry(library_interface_digest) {
190            Entry::Vacant(entry) => {
191                entry.insert(library);
192                self.link_assembled_modules(module_infos)
193            },
194            Entry::Occupied(mut entry) => {
195                let prev = entry.get_mut();
196
197                // If the same library is linked both dynamically and statically, prefer static
198                // linking always.
199                if matches!(prev.linkage, Linkage::Dynamic) {
200                    prev.linkage = library.linkage;
201                }
202
203                Ok(())
204            },
205        };
206
207        if result.is_ok()
208            && let Some(static_library) = static_library
209        {
210            self.static_libraries
211                .entry(static_library.commitment())
212                .or_insert(static_library);
213        }
214
215        result
216    }
217
218    /// Registers a set of MAST modules with the linker.
219    ///
220    /// If called directly, the modules will default to being dynamically linked. You must use
221    /// [`Self::link_library`] if you wish to statically link a set of assembled modules.
222    pub fn link_assembled_modules(
223        &mut self,
224        modules: impl IntoIterator<Item = ModuleInfo>,
225    ) -> Result<(), LinkerError> {
226        for module in modules {
227            self.link_assembled_module(module)?;
228        }
229
230        Ok(())
231    }
232
233    /// Registers a MAST module with the linker.
234    ///
235    /// If called directly, the module will default to being dynamically linked. You must use
236    /// [`Self::link_library`] if you wish to statically link `module`.
237    pub fn link_assembled_module(
238        &mut self,
239        module: ModuleInfo,
240    ) -> Result<ModuleIndex, LinkerError> {
241        log::debug!(target: "linker", "adding pre-assembled module {} to module graph", module.path());
242
243        let module_path = module.path();
244        let is_duplicate = self.find_module_index(module_path).is_some();
245        if is_duplicate {
246            return Err(LinkerError::DuplicateModule {
247                path: module_path.to_path_buf().into_boxed_path().into(),
248            });
249        }
250
251        let module_index = self.next_module_id();
252        let submodules = module.submodules().to_vec();
253        let items = module.items();
254        let mut symbols = Vec::with_capacity(items.len());
255        for (idx, item) in items {
256            let gid = module_index + idx;
257            self.callgraph.get_or_insert_node(gid);
258            match &item {
259                ItemInfo::Procedure(item) => {
260                    self.register_procedure_root(gid, item.digest);
261                },
262                ItemInfo::Constant(_) | ItemInfo::Type(_) => (),
263            }
264            symbols.push(Symbol::new(
265                item.name().clone(),
266                Visibility::Public,
267                LinkStatus::Linked,
268                SymbolItem::Compiled(item.clone()),
269            ));
270        }
271
272        let link_module = LinkModule::new(
273            module_index,
274            ast::ModuleKind::Library,
275            LinkStatus::Linked,
276            ModuleSource::Mast,
277            module_path.into(),
278        )
279        .with_submodules(submodules)
280        .with_symbols(symbols);
281
282        self.modules.push(link_module);
283        Ok(module_index)
284    }
285
286    /// Registers a set of AST modules with the linker.
287    ///
288    /// See [`Self::link_module`] for more details.
289    pub fn link_modules(
290        &mut self,
291        modules: impl IntoIterator<Item = Box<Module>>,
292    ) -> Result<Vec<ModuleIndex>, LinkerError> {
293        modules.into_iter().map(|mut m| self.link_module(&mut m)).collect()
294    }
295
296    /// Registers an AST module with the linker.
297    ///
298    /// A module provided to this method is presumed to be dynamically linked, unless specifically
299    /// handled otherwise by the assembler. In particular, the assembler will only statically link
300    /// the set of AST modules provided to [`Self::link`], as they are expected to comprise the
301    /// public interface of the assembled artifact.
302    ///
303    /// # Errors
304    ///
305    /// This operation can fail for the following reasons:
306    ///
307    /// * Module with same [Path] is in the graph already
308    /// * Too many modules in the graph
309    ///
310    /// # Panics
311    ///
312    /// This function will panic if the number of modules exceeds the maximum representable
313    /// [ModuleIndex] value, `u16::MAX`.
314    pub fn link_module(&mut self, module: &mut Module) -> Result<ModuleIndex, LinkerError> {
315        log::debug!(target: "linker", "adding unprocessed module {}", module.path());
316
317        let is_duplicate = self.find_module_index(module.path()).is_some();
318        if is_duplicate {
319            return Err(LinkerError::DuplicateModule { path: module.path().into() });
320        }
321
322        let module_index = self.next_module_id();
323        let submodules = module.submodules().to_vec();
324        let mut symbols = Vec::new();
325        let imports = module.take_imports().into_iter().map(Import::new).collect::<Vec<_>>();
326        for item in module.take_items() {
327            match item {
328                ast::Item::Type(item) => {
329                    let gid = module_index + ItemIndex::new(symbols.len());
330                    self.callgraph.get_or_insert_node(gid);
331                    symbols.push(Symbol::new(
332                        item.name().clone(),
333                        item.visibility(),
334                        LinkStatus::Unlinked,
335                        SymbolItem::Type(item),
336                    ));
337                },
338                ast::Item::Constant(item) => {
339                    let gid = module_index + ItemIndex::new(symbols.len());
340                    self.callgraph.get_or_insert_node(gid);
341                    symbols.push(Symbol::new(
342                        item.name().clone(),
343                        item.visibility,
344                        LinkStatus::Unlinked,
345                        SymbolItem::Constant(item),
346                    ));
347                },
348                ast::Item::Procedure(item) => {
349                    let gid = module_index + ItemIndex::new(symbols.len());
350                    self.callgraph.get_or_insert_node(gid);
351                    symbols.push(Symbol::new(
352                        item.name().clone().into(),
353                        item.visibility(),
354                        LinkStatus::Unlinked,
355                        SymbolItem::Procedure(RefCell::new(Box::new(item))),
356                    ));
357                },
358            }
359        }
360        let link_module = LinkModule::new(
361            module_index,
362            module.kind(),
363            LinkStatus::Unlinked,
364            ModuleSource::Ast,
365            module.path().into(),
366        )
367        .with_advice_map(module.advice_map().clone())
368        .with_submodules(submodules)
369        .with_imports(imports)
370        .with_symbols(symbols);
371
372        self.modules.push(link_module);
373        Ok(module_index)
374    }
375
376    #[inline]
377    fn next_module_id(&self) -> ModuleIndex {
378        ModuleIndex::new(self.modules.len())
379    }
380}
381
382// ------------------------------------------------------------------------------------------------
383/// Kernels
384impl Linker {
385    /// Returns a new [Linker] instantiated from the provided kernel and kernel info module.
386    ///
387    /// Note: it is assumed that kernel and kernel_module are consistent, but this is not checked.
388    pub fn with_kernel(
389        source_manager: Arc<dyn SourceManager>,
390        kernel_package: Arc<MastPackage>,
391    ) -> Result<Self, Report> {
392        log::debug!(target: "linker", "instantiating linker with kernel package {}@{}", kernel_package.name, kernel_package.version);
393
394        let mut linker = Self::new(source_manager);
395        linker.link_with_kernel(kernel_package)?;
396
397        Ok(linker)
398    }
399
400    /// Add a kernel to the linker after the linker is initially constructed.
401    ///
402    /// This cannot cause any issues with modules already added to the linker (if any), as they
403    /// cannot have directly depended on the kernel, or an error would have been raised.
404    ///
405    /// This will panic if the kernel is empty, or the provided kernel module info is not valid for
406    /// a kernel.
407    pub fn link_with_kernel(&mut self, kernel_package: Arc<MastPackage>) -> Result<(), Report> {
408        if !kernel_package.is_kernel() {
409            return Err(Report::msg("invalid kernel package: not a kernel"));
410        }
411        let kernel = kernel_package.to_kernel()?;
412        if kernel.is_empty() {
413            return Err(Report::msg("invalid kernel package: kernel cannot be empty"));
414        }
415        assert!(self.kernel.is_empty());
416        assert!(self.kernel_package.is_none());
417
418        log::debug!(target: "linker", "modifying linker with kernel package {}@{}", kernel_package.name, kernel_package.version);
419
420        let mut kernel_index = None;
421        let module_infos = kernel_package.try_module_infos().map_err(|err| {
422            LinkerError::InvalidPackageModuleSurface {
423                package: kernel_package.name.to_string(),
424                reason: err.to_string(),
425            }
426        })?;
427        for module_info in module_infos {
428            let is_kernel_module = module_info.path().is_kernel_path();
429            let module_index = self.link_assembled_module(module_info)?;
430            if is_kernel_module {
431                kernel_index = Some(module_index);
432            }
433        }
434        assert!(kernel_index.is_some());
435
436        self.kernel_index = kernel_index;
437        self.kernel = kernel;
438        self.kernel_package = Some(kernel_package);
439
440        Ok(())
441    }
442
443    pub fn kernel(&self) -> &Kernel {
444        &self.kernel
445    }
446
447    pub fn kernel_package(&self) -> Option<Arc<MastPackage>> {
448        self.kernel_package.clone()
449    }
450
451    pub fn has_nonempty_kernel(&self) -> bool {
452        self.kernel_index.is_some() || !self.kernel.is_empty()
453    }
454}
455
456// ------------------------------------------------------------------------------------------------
457/// Analysis
458impl Linker {
459    fn cycle_error(&self, cycle: CycleError) -> LinkerError {
460        let iter = cycle.into_node_ids();
461        let mut nodes = Vec::with_capacity(iter.len());
462        for node in iter {
463            let module = self[node.module].path();
464            let item = self[node].name();
465            nodes.push(module.join(item).to_string());
466        }
467        LinkerError::Cycle { nodes: nodes.into() }
468    }
469
470    /// Links the modules in `roots` and `support` using the current state of the linker.
471    ///
472    /// Returns the module indices corresponding to the public interface of the final assembled
473    /// artifact. This is determined by tracing the modules reachable from `roots` via their public
474    /// submodules. Any module in the graph reachable this way is returned as part of the public
475    /// interface.
476    pub fn link(
477        &mut self,
478        roots: impl IntoIterator<Item = Box<Module>>,
479        support: impl IntoIterator<Item = Box<Module>>,
480    ) -> Result<Vec<ModuleIndex>, LinkerError> {
481        use alloc::collections::BTreeSet;
482
483        let root_indices = self.link_modules(roots)?;
484        let _support_indices = self.link_modules(support)?;
485        let namespaces = NamespaceGraph::build(self)?;
486        let imports = namespaces.resolve_imports(self)?;
487
488        self.link_and_rewrite(&namespaces, &imports)?;
489
490        let mut reachable = BTreeSet::new();
491
492        for root in root_indices {
493            reachable.extend(namespaces.reachable_from_root(root));
494        }
495
496        Ok(reachable.into_iter().collect())
497    }
498
499    /// Links `kernel` using the current state of the linker.
500    ///
501    /// Returns the module index of the kernel module, which is expected to provide the public
502    /// interface of the final assembled kernel.
503    ///
504    /// This differs from `link` in that we allow all AST modules in the module graph access to
505    /// kernel features, e.g. `caller`, as if they are defined by the kernel module itself.
506    pub fn link_kernel(
507        &mut self,
508        mut kernel: Box<Module>,
509        support: impl IntoIterator<Item = Box<Module>>,
510    ) -> Result<Vec<ModuleIndex>, LinkerError> {
511        self.link_modules(support)?;
512        let original_module_len = self.modules.len();
513        let original_callgraph = self.callgraph.clone();
514        let module_index = self.link_module(&mut kernel)?;
515        let original_kernel_index = self.kernel_index;
516        let original_module_kinds = self
517            .modules
518            .iter()
519            .enumerate()
520            .take(module_index.as_usize())
521            .filter(|(_, module)| matches!(module.source(), ModuleSource::Ast))
522            .map(|(module_index, module)| (module_index, module.kind()))
523            .collect::<Vec<_>>();
524
525        // Set the module kind of all pending AST modules to Kernel, as we are linking a kernel
526        for module in self.modules.iter_mut().take(module_index.as_usize()) {
527            if matches!(module.source(), ModuleSource::Ast) {
528                module.set_kind(ast::ModuleKind::Kernel);
529            }
530        }
531
532        self.kernel_index = Some(module_index);
533
534        let result = (|| {
535            let namespaces = NamespaceGraph::build(self)?;
536            let imports = namespaces.resolve_imports(self)?;
537            self.link_and_rewrite(&namespaces, &imports)?;
538
539            Ok(namespaces.reachable_from_root(module_index))
540        })();
541
542        match result {
543            ok @ Ok(_) => ok,
544            err => {
545                self.kernel_index = original_kernel_index;
546                self.callgraph = original_callgraph;
547                self.modules.truncate(original_module_len);
548                for (module_index, module_kind) in original_module_kinds {
549                    self.modules[module_index].set_kind(module_kind);
550                }
551
552                err
553            },
554        }
555    }
556
557    /// Compute the module graph from the set of pending modules, and link it, rewriting any AST
558    /// modules with unresolved, or partially-resolved, symbol references.
559    ///
560    /// This should be called any time you add more libraries or modules to the module graph, to
561    /// ensure that the graph is valid, and that there are no unresolved references. In general,
562    /// you will only instantiate the linker, build up the graph, and link a single time; but you
563    /// can re-use the linker to build multiple artifacts as well.
564    ///
565    /// When this function is called, some initial information is calculated about the AST modules
566    /// which are to be added to the graph, and then each module is visited to perform a deeper
567    /// analysis than can be done by the `sema` module, as we now have the full set of modules
568    /// available to do import resolution, and to rewrite invoke targets with their absolute paths
569    /// and/or MAST roots. A variety of issues are caught at this stage.
570    ///
571    /// Once each module is validated, the various analysis results stored as part of the graph
572    /// structure are updated to reflect that module being added to the graph. Once part of the
573    /// graph, the module becomes immutable/clone-on-write, so as to allow the graph to be
574    /// cheaply cloned.
575    ///
576    /// The final, and most important, analysis done by this function is the topological sort of
577    /// the global call graph, which contains the inter-procedural dependencies of every procedure
578    /// in the module graph. We use this sort order to do two things:
579    ///
580    /// 1. Verify that there are no static cycles in the graph that would prevent us from being able
581    ///    to hash the generated MAST of the program. NOTE: dynamic cycles, e.g. those induced by
582    ///    `dynexec`, are perfectly fine, we are only interested in preventing cycles that interfere
583    ///    with the ability to generate MAST roots.
584    ///
585    /// 2. Visit the call graph bottom-up, so that we can fully compile a procedure before any of
586    ///    its callers, and thus rewrite those callers to reference that procedure by MAST root,
587    ///    rather than by name. As a result, a compiled MAST program is like an immutable snapshot
588    ///    of the entire call graph at the time of compilation. Later, if we choose to recompile a
589    ///    subset of modules (currently we do not have support for this in the assembler API), we
590    ///    can re-analyze/re-compile only those parts of the graph which have actually changed.
591    ///
592    /// NOTE: This will return `Err` if we detect a validation error, a cycle in the graph, or an
593    /// operation not supported by the current configuration. Basically, for any reason that would
594    /// cause the resulting graph to represent an invalid program.
595    fn link_and_rewrite(
596        &mut self,
597        namespaces: &NamespaceGraph,
598        imports: &ResolvedImports,
599    ) -> Result<(), LinkerError> {
600        log::debug!(
601            target: "linker",
602            "processing {} unlinked/partially-linked modules, and recomputing module graph",
603            self.modules.iter().filter(|m| !m.is_linked()).count()
604        );
605
606        // It is acceptable for there to be no changes, but if the graph is empty and no changes
607        // are being made, we treat that as an error
608        if self.modules.is_empty() {
609            return Err(LinkerError::Empty);
610        }
611
612        // If no changes are being made, we're done
613        if self.modules.iter().all(LinkModule::is_linked) {
614            return Ok(());
615        }
616
617        // Obtain a set of resolvers for the pending modules so that we can do name resolution
618        // before they are added to the graph
619        let pending_modules = self
620            .modules
621            .iter()
622            .enumerate()
623            .filter(|(_, module)| module.is_unlinked())
624            .map(|(module_index, module)| (module_index, module.clone()))
625            .collect::<Vec<_>>();
626        let original_callgraph = self.callgraph.clone();
627
628        let result = {
629            let resolver = SymbolResolver::with_namespaces(self, namespaces, imports);
630            let mut edges = Vec::new();
631            let mut cache = ResolverCache::default();
632            let mut linked_modules = Vec::new();
633
634            for (module_index, module) in self.modules.iter().enumerate() {
635                if !module.is_unlinked() {
636                    continue;
637                }
638
639                let module_index = ModuleIndex::new(module_index);
640
641                for import in module.imports() {
642                    if let Some(namespaces::ResolvedUse::Item(gid)) =
643                        imports.get(module_index, import.local_name().as_str())
644                    {
645                        import.set_resolved(gid);
646                    }
647                }
648
649                for (symbol_idx, symbol) in module.symbols().enumerate() {
650                    let gid = module_index + ItemIndex::new(symbol_idx);
651
652                    // Perform any applicable rewrites to this item
653                    rewrites::rewrite_symbol(gid, symbol, &resolver, &mut cache)?;
654
655                    // Update the linker graph
656                    match symbol.item() {
657                        SymbolItem::Compiled(_) | SymbolItem::Type(_) | SymbolItem::Constant(_) => {
658                        },
659                        SymbolItem::Procedure(proc) => {
660                            // Add edges to all transitive dependencies of this item due to
661                            // calls/symbol refs
662                            let proc = proc.borrow();
663                            for invoke in proc.invoked() {
664                                log::debug!(target: "linker", "  | recording {} dependency on {}", invoke.kind, invoke.target);
665
666                                let context = SymbolResolutionContext {
667                                    span: invoke.span(),
668                                    module: module_index,
669                                    kind: Some(invoke.kind),
670                                };
671                                if let Some(callee) = resolver
672                                    .resolve_invoke_target(&context, &invoke.target)?
673                                    .into_global_id()
674                                {
675                                    log::debug!(
676                                        target: "linker",
677                                        "  | resolved dependency to gid {}:{}",
678                                        callee.module.as_usize(),
679                                        callee.index.as_usize()
680                                    );
681                                    edges.push((gid, callee));
682                                }
683                            }
684                        },
685                    }
686                }
687
688                linked_modules.push(module_index);
689            }
690
691            let mut callgraph = self.callgraph.clone();
692            for (caller, callee) in edges {
693                callgraph.add_edge(caller, callee).map_err(|cycle| self.cycle_error(cycle))?;
694            }
695
696            // Make sure the graph is free of cycles
697            callgraph.toposort().map_err(|cycle| self.cycle_error(cycle))?;
698
699            Ok::<_, LinkerError>((linked_modules, callgraph))
700        };
701
702        match result {
703            Ok((linked_modules, callgraph)) => {
704                self.callgraph = callgraph;
705                for module_index in linked_modules {
706                    self.modules[module_index.as_usize()].set_status(LinkStatus::Linked);
707                }
708            },
709            Err(err) => {
710                self.callgraph = original_callgraph;
711                for (module_index, module) in pending_modules {
712                    self.modules[module_index] = module;
713                }
714                return Err(err);
715            },
716        }
717
718        Ok(())
719    }
720}
721
722// ------------------------------------------------------------------------------------------------
723/// Accessors/Queries
724impl Linker {
725    /// Get access to all module information maintained by the linker
726    pub fn modules(&self) -> &[LinkModule] {
727        self.modules.as_slice()
728    }
729
730    /// Get an iterator over the external libraries the linker has linked against
731    pub fn libraries(&self) -> impl Iterator<Item = &LinkLibrary> {
732        self.libraries.values()
733    }
734
735    /// Get an iterator over the static libraries used to build the final MAST forest.
736    pub fn static_libraries(&self) -> impl Iterator<Item = &LinkLibrary> {
737        self.static_libraries.values()
738    }
739
740    /// Compute the topological sort of the callgraph rooted at `caller`
741    pub fn topological_sort_from_root(
742        &self,
743        caller: GlobalItemIndex,
744    ) -> Result<Vec<GlobalItemIndex>, CycleError> {
745        self.callgraph.toposort_caller(caller)
746    }
747
748    /// Returns a procedure index which corresponds to the provided procedure digest.
749    ///
750    /// Note that there can be many procedures with the same digest. This method returns an
751    /// arbitrary one.
752    pub fn get_procedure_index_by_digest(
753        &self,
754        procedure_digest: &Word,
755    ) -> Option<GlobalItemIndex> {
756        self.procedures_by_mast_root.get(procedure_digest).map(|indices| indices[0])
757    }
758
759    /// Returns a conflicting export root when a dynamic library cannot identify an exact procedure
760    /// by digest alone.
761    pub fn conflicting_dynamic_procedure_export_root(
762        &self,
763        source_library_commitment: Word,
764        mast_root: Word,
765        selected_root_id: MastNodeId,
766    ) -> Option<MastNodeId> {
767        let library = self.libraries.get(&source_library_commitment)?;
768        if !matches!(library.linkage, Linkage::Dynamic) {
769            return None;
770        }
771
772        library
773            .module_infos()
774            .ok()?
775            .into_iter()
776            .flat_map(|module| {
777                module
778                    .procedures()
779                    .filter_map(|(_, proc)| {
780                        (proc.digest == mast_root).then(|| proc.source_root_id()).flatten()
781                    })
782                    .collect::<Vec<_>>()
783            })
784            .find(|&root_id| root_id != selected_root_id)
785    }
786
787    /// Resolves `target` from the perspective of `caller`.
788    pub fn resolve_invoke_target(
789        &self,
790        caller: &SymbolResolutionContext,
791        target: &InvocationTarget,
792    ) -> Result<SymbolResolution, LinkerError> {
793        let namespaces = NamespaceGraph::build(self)?;
794        let imports = namespaces.resolve_imports(self)?;
795        let resolver = SymbolResolver::with_namespaces(self, &namespaces, &imports);
796        resolver.resolve_invoke_target(caller, target)
797    }
798
799    /// Resolves `path` from the perspective of `caller`.
800    pub fn resolve_path(
801        &self,
802        caller: &SymbolResolutionContext,
803        path: &Path,
804    ) -> Result<SymbolResolution, LinkerError> {
805        let namespaces = NamespaceGraph::build(self)?;
806        let imports = namespaces.resolve_imports(self)?;
807        let resolver = SymbolResolver::with_namespaces(self, &namespaces, &imports);
808        resolver.resolve_path(caller, Span::new(caller.span, path))
809    }
810
811    /// Resolves the user-defined type signature of the given procedure to the HIR type signature
812    pub fn resolve_signature(
813        &self,
814        gid: GlobalItemIndex,
815    ) -> Result<Option<Arc<types::FunctionType>>, LinkerError> {
816        match self[gid].item() {
817            SymbolItem::Compiled(ItemInfo::Procedure(proc)) => Ok(proc.signature.clone()),
818            SymbolItem::Procedure(proc) => {
819                let proc = proc.borrow();
820                match proc.signature() {
821                    Some(ty) => self.translate_function_type(gid.module, ty).map(Some),
822                    None => Ok(None),
823                }
824            },
825            SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
826                panic!("procedure index unexpectedly refers to non-procedure item")
827            },
828        }
829    }
830
831    fn translate_function_type(
832        &self,
833        module_index: ModuleIndex,
834        ty: &ast::FunctionType,
835    ) -> Result<Arc<types::FunctionType>, LinkerError> {
836        use miden_assembly_syntax::ast::TypeResolver;
837
838        let cc = ty.cc;
839        let mut args = Vec::with_capacity(ty.args.len());
840
841        let symbol_resolver = SymbolResolver::new(self);
842        let mut cache = ResolverCache::default();
843        let mut resolver = Resolver {
844            resolver: &symbol_resolver,
845            cache: &mut cache,
846            current_module: module_index,
847        };
848        for arg in ty.args.iter() {
849            if let Some(arg) = resolver.resolve(arg)? {
850                args.push(arg);
851            } else {
852                let span = arg.span();
853                return Err(LinkerError::UndefinedType {
854                    span,
855                    source_file: self.source_manager.get(span.source_id()).ok(),
856                });
857            }
858        }
859        let mut results = Vec::with_capacity(ty.results.len());
860        for result in ty.results.iter() {
861            if let Some(result) = resolver.resolve(result)? {
862                results.push(result);
863            } else {
864                let span = result.span();
865                return Err(LinkerError::UndefinedType {
866                    span,
867                    source_file: self.source_manager.get(span.source_id()).ok(),
868                });
869            }
870        }
871        Ok(Arc::new(types::FunctionType::new(cc, args, results)))
872    }
873
874    /// Resolves a [GlobalItemIndex] to the known attributes of that procedure
875    pub fn resolve_attributes(&self, gid: GlobalItemIndex) -> AttributeSet {
876        match self[gid].item() {
877            SymbolItem::Compiled(ItemInfo::Procedure(proc)) => proc.attributes.clone(),
878            SymbolItem::Procedure(proc) => {
879                let proc = proc.borrow();
880                proc.attributes().clone()
881            },
882            SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
883                panic!("procedure index unexpectedly refers to non-procedure item")
884            },
885        }
886    }
887
888    /// Resolves a [GlobalItemIndex] to a concrete [ast::types::Type]
889    pub fn resolve_type(
890        &self,
891        span: SourceSpan,
892        gid: GlobalItemIndex,
893    ) -> Result<types::Type, LinkerError> {
894        use miden_assembly_syntax::ast::TypeResolver;
895
896        let symbol_resolver = SymbolResolver::new(self);
897        let mut cache = ResolverCache::default();
898        let mut resolver = Resolver {
899            cache: &mut cache,
900            resolver: &symbol_resolver,
901            current_module: gid.module,
902        };
903
904        resolver.get_type(span, gid)
905    }
906
907    /// Registers a [MastNodeId] as corresponding to a given [GlobalProcedureIndex].
908    ///
909    /// # SAFETY
910    ///
911    /// It is essential that the caller _guarantee_ that the given digest belongs to the specified
912    /// procedure. It is fine if there are multiple procedures with the same digest, but it _must_
913    /// be the case that if a given digest is specified, it can be used as if it was the definition
914    /// of the referenced procedure, i.e. they are referentially transparent.
915    pub(crate) fn register_procedure_root(
916        &mut self,
917        id: GlobalItemIndex,
918        procedure_mast_root: Word,
919    ) {
920        use alloc::collections::btree_map::Entry;
921        match self.procedures_by_mast_root.entry(procedure_mast_root) {
922            Entry::Occupied(ref mut entry) => {
923                let prev_id = entry.get()[0];
924                if prev_id != id {
925                    // Multiple procedures with the same root, but compatible
926                    entry.get_mut().push(id);
927                }
928            },
929            Entry::Vacant(entry) => {
930                entry.insert(smallvec![id]);
931            },
932        }
933    }
934
935    /// Resolve a [Path] to a [ModuleIndex] in this graph
936    pub fn find_module_index(&self, path: &Path) -> Option<ModuleIndex> {
937        self.modules.iter().position(|m| path == m.path()).map(ModuleIndex::new)
938    }
939
940    /// Resolve a [Path] to a [Module] in this graph
941    pub fn find_module(&self, path: &Path) -> Option<&LinkModule> {
942        self.modules.iter().find(|m| path == m.path())
943    }
944}
945
946/// Const evaluation
947impl Linker {
948    /// Evaluate `expr` to a concrete constant value, in the context of the given item.
949    pub fn const_eval(
950        &self,
951        gid: GlobalItemIndex,
952        expr: &ast::ConstantExpr,
953        cache: &mut ResolverCache,
954    ) -> Result<ast::ConstantValue, LinkerError> {
955        let symbol_resolver = SymbolResolver::new(self);
956        let mut resolver = Resolver {
957            resolver: &symbol_resolver,
958            cache,
959            current_module: gid.module,
960        };
961
962        ast::constants::eval::expr(expr, &mut resolver).map(|expr| expr.expect_value())
963    }
964}
965
966impl Index<ModuleIndex> for Linker {
967    type Output = LinkModule;
968
969    fn index(&self, index: ModuleIndex) -> &Self::Output {
970        &self.modules[index.as_usize()]
971    }
972}
973
974impl Index<GlobalItemIndex> for Linker {
975    type Output = Symbol;
976
977    fn index(&self, index: GlobalItemIndex) -> &Self::Output {
978        &self.modules[index.module.as_usize()][index.index]
979    }
980}
981
982#[cfg(test)]
983mod tests {
984    use std::{
985        panic::{AssertUnwindSafe, catch_unwind},
986        string::String,
987        sync::Arc,
988    };
989
990    use miden_assembly_syntax::{
991        ast::{
992            Ident, InvocationTarget, InvokeKind, ItemIndex, Path, SymbolResolutionError,
993            Visibility, types,
994        },
995        debuginfo::{SourceSpan, Span},
996        module::{ItemInfo, TypeInfo},
997    };
998    use miden_core::Felt;
999
1000    use super::*;
1001    use crate::{
1002        Assembler,
1003        ast::Module,
1004        testing::{TestContext, source_file},
1005    };
1006
1007    #[test]
1008    fn failed_kernel_link_restores_kernel_state() {
1009        let context = TestContext::default();
1010        let source_manager = context.source_manager();
1011        let kernel_source = r#"
1012                pub proc a
1013                    call.b
1014                end
1015
1016                proc b
1017                    call.a
1018                end
1019                "#;
1020
1021        let userspace = context
1022            .parse_module(source_file!(
1023                &context,
1024                r#"
1025                    namespace userspace
1026
1027                    pub proc helper
1028                        push.1
1029                    end
1030                    "#
1031            ))
1032            .expect("userspace module parsing must succeed");
1033
1034        let mut linker = Linker::new(source_manager);
1035        let userspace_index = linker
1036            .link([userspace], None)
1037            .expect("userspace module must link successfully")
1038            .into_iter()
1039            .next()
1040            .expect("linked module index must be returned");
1041
1042        let first_err = linker
1043            .link_kernel(
1044                context
1045                    .parse_kernel(source_file!(&context, kernel_source))
1046                    .expect("kernel parsing must succeed"),
1047                None,
1048            )
1049            .expect_err("expected cyclic kernel to be rejected");
1050
1051        assert!(first_err.to_string().contains("found a cycle in the call graph"));
1052        assert!(!linker.has_nonempty_kernel(), "failed kernel link must not leave a kernel set");
1053        assert_eq!(linker[userspace_index].kind(), ast::ModuleKind::Library);
1054
1055        let second_err = linker
1056            .link_kernel(
1057                context
1058                    .parse_kernel(source_file!(&context, kernel_source))
1059                    .expect("kernel parsing must succeed"),
1060                None,
1061            )
1062            .expect_err("expected cyclic kernel retry to be rejected");
1063        assert!(second_err.to_string().contains("found a cycle in the call graph"));
1064        assert!(!second_err.to_string().contains("duplicate module"));
1065
1066        let syscall_context = SymbolResolutionContext {
1067            span: SourceSpan::UNKNOWN,
1068            module: userspace_index,
1069            kind: Some(InvokeKind::SysCall),
1070        };
1071        let err = linker
1072            .resolve_invoke_target(
1073                &syscall_context,
1074                &InvocationTarget::Symbol(Ident::new("a").expect("valid identifier")),
1075            )
1076            .expect_err("expected syscall without a linked kernel to be rejected");
1077        assert!(matches!(err, LinkerError::InvalidSysCallTarget { .. }));
1078    }
1079
1080    #[test]
1081    fn link_library_keeps_same_interface_libraries_with_distinct_forest_commitments() {
1082        let context = TestContext::default();
1083        let module = context
1084            .parse_module(source_file!(
1085                &context,
1086                r#"
1087                namespace lib
1088
1089                pub proc foo
1090                    push.1
1091                end
1092                "#
1093            ))
1094            .expect("library module should parse");
1095        let package: Arc<MastPackage> = Assembler::new(context.source_manager())
1096            .assemble_library("lib", module, None::<Box<Module>>)
1097            .expect("library should assemble")
1098            .into();
1099        let with_advice = Arc::new(package.as_ref().clone().with_advice_map(AdviceMap::from_iter(
1100            [(Word::from([1_u32, 2, 3, 4]), vec![Felt::from_u32(5)])],
1101        )));
1102
1103        assert_ne!(package.digest(), with_advice.digest());
1104        assert_eq!(package.interface_digest().unwrap(), with_advice.interface_digest().unwrap());
1105        assert_ne!(package.mast_forest().commitment(), with_advice.mast_forest().commitment());
1106
1107        let mut linker = Linker::new(context.source_manager());
1108        linker
1109            .link_library(LinkLibrary::from_package(package).with_linkage(Linkage::Static))
1110            .expect("first library should link");
1111        linker
1112            .link_library(LinkLibrary::from_package(with_advice).with_linkage(Linkage::Static))
1113            .expect("same public interface with distinct forest commitment should link");
1114
1115        assert_eq!(linker.libraries().count(), 1);
1116        assert_eq!(linker.static_libraries().count(), 2);
1117    }
1118
1119    #[test]
1120    fn oversized_link_module_resolution_returns_structured_error() {
1121        let context = TestContext::default();
1122        let mut linker = Linker::new(context.source_manager());
1123        let module_id = ModuleIndex::new(0);
1124        let path = Arc::<Path>::from(Path::new("::m::huge"));
1125        let mut symbols = Vec::with_capacity(ItemIndex::MAX_ITEMS + 1);
1126
1127        for i in 0..=ItemIndex::MAX_ITEMS {
1128            let name = Ident::new(format!("a{i}")).expect("valid identifier");
1129            symbols.push(Symbol::new(
1130                name.clone(),
1131                Visibility::Private,
1132                LinkStatus::Unlinked,
1133                SymbolItem::Compiled(ItemInfo::Type(TypeInfo { name, ty: types::Type::Felt })),
1134            ));
1135        }
1136
1137        linker.modules.push(
1138            LinkModule::new(
1139                module_id,
1140                ast::ModuleKind::Library,
1141                LinkStatus::Unlinked,
1142                ModuleSource::Mast,
1143                path,
1144            )
1145            .with_symbols(symbols),
1146        );
1147
1148        let result = catch_unwind(AssertUnwindSafe(|| {
1149            linker[module_id].resolve(Span::unknown("a0"), &SymbolResolver::new(&linker))
1150        }));
1151
1152        let result = match result {
1153            Ok(result) => result,
1154            Err(panic) => {
1155                let message = panic
1156                    .downcast_ref::<&str>()
1157                    .copied()
1158                    .or_else(|| panic.downcast_ref::<String>().map(String::as_str))
1159                    .expect("panic payload should be a string");
1160                panic!("expected graceful error, got panic: {message}");
1161            },
1162        };
1163
1164        assert!(matches!(
1165            result,
1166            Err(err) if matches!(*err, SymbolResolutionError::TooManyItemsInModule { .. })
1167        ));
1168    }
1169}