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, AliasTarget, 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) = resolver
513 .resolve_alias_target(&context, alias.target())?
514 .into_global_id()
515 {
516 log::debug!(
517 target: "linker",
518 " | resolved alias to gid {:?}:{:?}",
519 callee.module,
520 callee.index
521 );
522 edges.push((gid, callee));
523 resolved.set(Some(callee));
524 }
525 }
526 },
527 SymbolItem::Procedure(proc) => {
528 // Add edges to all transitive dependencies of this item due to calls/symbol
529 // refs
530 let proc = proc.borrow();
531 for invoke in proc.invoked() {
532 log::debug!(target: "linker", " | recording {} dependency on {}", invoke.kind, &invoke.target);
533
534 let context = SymbolResolutionContext {
535 span: invoke.span(),
536 module: module_index,
537 kind: None,
538 };
539 if let Some(callee) = resolver
540 .resolve_invoke_target(&context, &invoke.target)?
541 .into_global_id()
542 {
543 log::debug!(
544 target: "linker",
545 " | resolved dependency to gid {}:{}",
546 callee.module.as_usize(),
547 callee.index.as_usize()
548 );
549 edges.push((gid, callee));
550 }
551 }
552 },
553 }
554 }
555
556 module.set_status(LinkStatus::Linked);
557 }
558
559 edges
560 .into_iter()
561 .for_each(|(caller, callee)| self.callgraph.add_edge(caller, callee));
562
563 // Make sure the graph is free of cycles
564 self.callgraph.toposort().map_err(|cycle| {
565 let iter = cycle.into_node_ids();
566 let mut nodes = Vec::with_capacity(iter.len());
567 for node in iter {
568 let module = self[node.module].path();
569 let item = self[node].name();
570 nodes.push(module.join(item).to_string());
571 }
572 LinkerError::Cycle { nodes: nodes.into() }
573 })?;
574
575 Ok(())
576 }
577}
578
579// ------------------------------------------------------------------------------------------------
580/// Accessors/Queries
581impl Linker {
582 /// Get an iterator over the external libraries the linker has linked against
583 pub fn libraries(&self) -> impl Iterator<Item = &LinkLibrary> {
584 self.libraries.values()
585 }
586
587 /// Compute the topological sort of the callgraph rooted at `caller`
588 pub fn topological_sort_from_root(
589 &self,
590 caller: GlobalItemIndex,
591 ) -> Result<Vec<GlobalItemIndex>, CycleError> {
592 self.callgraph.toposort_caller(caller)
593 }
594
595 /// Returns a procedure index which corresponds to the provided procedure digest.
596 ///
597 /// Note that there can be many procedures with the same digest - due to having the same code,
598 /// and/or using different decorators which don't affect the MAST root. This method returns an
599 /// arbitrary one.
600 pub fn get_procedure_index_by_digest(
601 &self,
602 procedure_digest: &Word,
603 ) -> Option<GlobalItemIndex> {
604 self.procedures_by_mast_root.get(procedure_digest).map(|indices| indices[0])
605 }
606
607 /// Resolves `target` from the perspective of `caller`.
608 pub fn resolve_invoke_target(
609 &self,
610 caller: &SymbolResolutionContext,
611 target: &InvocationTarget,
612 ) -> Result<SymbolResolution, LinkerError> {
613 let resolver = SymbolResolver::new(self);
614 resolver.resolve_invoke_target(caller, target)
615 }
616
617 /// Resolves `target` from the perspective of `caller`.
618 pub fn resolve_alias_target(
619 &self,
620 caller: &SymbolResolutionContext,
621 target: &AliasTarget,
622 ) -> Result<SymbolResolution, LinkerError> {
623 let resolver = SymbolResolver::new(self);
624 resolver.resolve_alias_target(caller, target)
625 }
626
627 /// Resolves `path` from the perspective of `caller`.
628 pub fn resolve_path(
629 &self,
630 caller: &SymbolResolutionContext,
631 path: &Path,
632 ) -> Result<SymbolResolution, LinkerError> {
633 let resolver = SymbolResolver::new(self);
634 resolver.resolve_path(caller, Span::new(caller.span, path))
635 }
636
637 /// Resolves the user-defined type signature of the given procedure to the HIR type signature
638 pub(super) fn resolve_signature(
639 &self,
640 gid: GlobalItemIndex,
641 ) -> Result<Option<Arc<types::FunctionType>>, LinkerError> {
642 match self[gid].item() {
643 SymbolItem::Compiled(ItemInfo::Procedure(proc)) => Ok(proc.signature.clone()),
644 SymbolItem::Procedure(proc) => {
645 let proc = proc.borrow();
646 match proc.signature() {
647 Some(ty) => self.translate_function_type(gid.module, ty).map(Some),
648 None => Ok(None),
649 }
650 },
651 SymbolItem::Alias { alias, resolved } => {
652 if let Some(resolved) = resolved.get() {
653 return self.resolve_signature(resolved);
654 }
655
656 let context = SymbolResolutionContext {
657 span: alias.target().span(),
658 module: gid.module,
659 kind: Some(InvokeKind::ProcRef),
660 };
661 let resolution = self.resolve_alias_target(&context, alias.target())?;
662 match resolution {
663 // If we get back a MAST root resolution, it's a phantom digest
664 SymbolResolution::MastRoot(_) => Ok(None),
665 SymbolResolution::Exact { gid, .. } => self.resolve_signature(gid),
666 SymbolResolution::Module { .. }
667 | SymbolResolution::Local(_)
668 | SymbolResolution::External(_) => unreachable!(),
669 }
670 },
671 SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
672 panic!("procedure index unexpectedly refers to non-procedure item")
673 },
674 }
675 }
676
677 fn translate_function_type(
678 &self,
679 module_index: ModuleIndex,
680 ty: &ast::FunctionType,
681 ) -> Result<Arc<types::FunctionType>, LinkerError> {
682 use miden_assembly_syntax::ast::TypeResolver;
683
684 let cc = ty.cc;
685 let mut args = Vec::with_capacity(ty.args.len());
686
687 let symbol_resolver = SymbolResolver::new(self);
688 let mut cache = ResolverCache::default();
689 let resolver = Resolver {
690 resolver: &symbol_resolver,
691 cache: &mut cache,
692 current_module: module_index,
693 };
694 for arg in ty.args.iter() {
695 if let Some(arg) = resolver.resolve(arg)? {
696 args.push(arg);
697 } else {
698 let span = arg.span();
699 return Err(LinkerError::UndefinedType {
700 span,
701 source_file: self.source_manager.get(span.source_id()).ok(),
702 });
703 }
704 }
705 let mut results = Vec::with_capacity(ty.results.len());
706 for result in ty.results.iter() {
707 if let Some(result) = resolver.resolve(result)? {
708 results.push(result);
709 } else {
710 let span = result.span();
711 return Err(LinkerError::UndefinedType {
712 span,
713 source_file: self.source_manager.get(span.source_id()).ok(),
714 });
715 }
716 }
717 Ok(Arc::new(types::FunctionType::new(cc, args, results)))
718 }
719
720 /// Resolves a [GlobalProcedureIndex] to the known attributes of that procedure
721 pub(super) fn resolve_attributes(
722 &self,
723 gid: GlobalItemIndex,
724 ) -> Result<AttributeSet, LinkerError> {
725 match self[gid].item() {
726 SymbolItem::Compiled(ItemInfo::Procedure(proc)) => Ok(proc.attributes.clone()),
727 SymbolItem::Procedure(proc) => {
728 let proc = proc.borrow();
729 Ok(proc.attributes().clone())
730 },
731 SymbolItem::Alias { alias, resolved } => {
732 if let Some(resolved) = resolved.get() {
733 return self.resolve_attributes(resolved);
734 }
735
736 let context = SymbolResolutionContext {
737 span: alias.target().span(),
738 module: gid.module,
739 kind: Some(InvokeKind::ProcRef),
740 };
741 let resolution = self.resolve_alias_target(&context, alias.target())?;
742 match resolution {
743 SymbolResolution::MastRoot(_)
744 | SymbolResolution::Local(_)
745 | SymbolResolution::External(_) => Ok(AttributeSet::default()),
746 SymbolResolution::Exact { gid, .. } => self.resolve_attributes(gid),
747 SymbolResolution::Module { .. } => {
748 unreachable!("expected resolver to raise error")
749 },
750 }
751 },
752 SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
753 panic!("procedure index unexpectedly refers to non-procedure item")
754 },
755 }
756 }
757
758 /// Resolves a [GlobalItemIndex] to a concrete [ast::types::Type]
759 pub(super) fn resolve_type(
760 &self,
761 span: SourceSpan,
762 gid: GlobalItemIndex,
763 ) -> Result<ast::types::Type, LinkerError> {
764 use miden_assembly_syntax::ast::TypeResolver;
765
766 let symbol_resolver = SymbolResolver::new(self);
767 let mut cache = ResolverCache::default();
768 let resolver = Resolver {
769 cache: &mut cache,
770 resolver: &symbol_resolver,
771 current_module: gid.module,
772 };
773
774 resolver.get_type(span, gid)
775 }
776
777 /// Registers a [MastNodeId] as corresponding to a given [GlobalProcedureIndex].
778 ///
779 /// # SAFETY
780 ///
781 /// It is essential that the caller _guarantee_ that the given digest belongs to the specified
782 /// procedure. It is fine if there are multiple procedures with the same digest, but it _must_
783 /// be the case that if a given digest is specified, it can be used as if it was the definition
784 /// of the referenced procedure, i.e. they are referentially transparent.
785 pub(crate) fn register_procedure_root(
786 &mut self,
787 id: GlobalItemIndex,
788 procedure_mast_root: Word,
789 ) -> Result<(), LinkerError> {
790 use alloc::collections::btree_map::Entry;
791 match self.procedures_by_mast_root.entry(procedure_mast_root) {
792 Entry::Occupied(ref mut entry) => {
793 let prev_id = entry.get()[0];
794 if prev_id != id {
795 // Multiple procedures with the same root, but compatible
796 entry.get_mut().push(id);
797 }
798 },
799 Entry::Vacant(entry) => {
800 entry.insert(smallvec![id]);
801 },
802 }
803
804 Ok(())
805 }
806
807 /// Resolve a [Path] to a [ModuleIndex] in this graph
808 pub fn find_module_index(&self, path: &Path) -> Option<ModuleIndex> {
809 self.modules.iter().position(|m| path == m.path()).map(ModuleIndex::new)
810 }
811
812 /// Resolve a [Path] to a [Module] in this graph
813 pub fn find_module(&self, path: &Path) -> Option<&LinkModule> {
814 self.modules.iter().find(|m| path == m.path())
815 }
816}
817
818/// Const evaluation
819impl Linker {
820 /// Evaluate `expr` to a concrete constant value, in the context of the given item.
821 pub(super) fn const_eval(
822 &self,
823 gid: GlobalItemIndex,
824 expr: &ast::ConstantExpr,
825 cache: &mut ResolverCache,
826 ) -> Result<ast::ConstantValue, LinkerError> {
827 let symbol_resolver = SymbolResolver::new(self);
828 let mut resolver = Resolver {
829 resolver: &symbol_resolver,
830 cache,
831 current_module: gid.module,
832 };
833
834 ast::constants::eval::expr(expr, &mut resolver).map(|expr| expr.expect_value())
835 }
836}
837
838impl Index<ModuleIndex> for Linker {
839 type Output = LinkModule;
840
841 fn index(&self, index: ModuleIndex) -> &Self::Output {
842 &self.modules[index.as_usize()]
843 }
844}
845
846impl Index<GlobalItemIndex> for Linker {
847 type Output = Symbol;
848
849 fn index(&self, index: GlobalItemIndex) -> &Self::Output {
850 &self.modules[index.module.as_usize()][index.index]
851 }
852}