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