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