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