miden_assembly/linker/mod.rs
1mod analysis;
2mod callgraph;
3mod debug;
4mod errors;
5mod name_resolver;
6mod rewrites;
7
8use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec};
9use core::ops::Index;
10
11use miden_core::{Kernel, Word};
12use smallvec::{SmallVec, smallvec};
13
14use self::{analysis::MaybeRewriteCheck, rewrites::ModuleRewriter};
15pub use self::{
16 callgraph::{CallGraph, CycleError},
17 errors::LinkerError,
18 name_resolver::{CallerInfo, NameResolver, ResolvedTarget},
19};
20use super::{GlobalProcedureIndex, ModuleIndex};
21use crate::{
22 Library, LibraryNamespace, LibraryPath, SourceManager, Spanned,
23 ast::{
24 Export, InvocationTarget, InvokeKind, Module, ProcedureIndex, ProcedureName,
25 ResolvedProcedure,
26 },
27 library::{ModuleInfo, ProcedureInfo},
28};
29
30// LINKER INPUTS
31// ================================================================================================
32
33/// Represents a linked procedure in the procedure graph of the [`Linker`]
34pub enum ProcedureLink<'a> {
35 /// A procedure which we have the original AST for, and may require additional processing
36 Ast(&'a Export),
37 /// A procedure which we have the MAST for, no additional processing required
38 Info(&'a ProcedureInfo),
39}
40
41impl ProcedureLink<'_> {
42 /// Returns the name of the procedure.
43 pub fn name(&self) -> &ProcedureName {
44 match self {
45 Self::Ast(p) => p.name(),
46 Self::Info(p) => &p.name,
47 }
48 }
49
50 /// Returns the wrapped procedure if in the `Ast` representation, or panics otherwise.
51 ///
52 /// # Panics
53 /// - Panics if the wrapped procedure is not in the `Ast` representation.
54 pub fn unwrap_ast(&self) -> &Export {
55 match self {
56 Self::Ast(proc) => proc,
57 Self::Info(_) => panic!("expected AST procedure, but was compiled"),
58 }
59 }
60
61 /// Returns true if the wrapped procedure is in the `Ast` representation.
62 pub fn is_ast(&self) -> bool {
63 matches!(self, Self::Ast(_))
64 }
65}
66
67/// Represents a linked module in the module graph of the [`Linker`]
68#[derive(Clone)]
69pub enum ModuleLink {
70 /// A module which we have the original AST for, and may require additional processing
71 Ast(Arc<Module>),
72 /// A previously-assembled module we have MAST for, no additional processing required
73 Info(ModuleInfo),
74}
75
76impl ModuleLink {
77 /// Returns the library path of the wrapped module.
78 pub fn path(&self) -> &LibraryPath {
79 match self {
80 Self::Ast(m) => m.path(),
81 Self::Info(m) => m.path(),
82 }
83 }
84
85 /// Returns the wrapped module if in the `Ast` representation, or panics otherwise.
86 ///
87 /// # Panics
88 /// - Panics if the wrapped module is not in the `Ast` representation.
89 pub fn unwrap_ast(&self) -> &Arc<Module> {
90 match self {
91 Self::Ast(module) => module,
92 Self::Info(_) => {
93 panic!("expected module to be in AST representation, but was compiled")
94 },
95 }
96 }
97
98 /// Resolves `name` to a procedure within the local scope of this module.
99 pub fn resolve(&self, name: &ProcedureName) -> Option<ResolvedProcedure> {
100 match self {
101 ModuleLink::Ast(module) => module.resolve(name),
102 ModuleLink::Info(module) => {
103 module.get_procedure_digest_by_name(name).map(ResolvedProcedure::MastRoot)
104 },
105 }
106 }
107}
108
109/// Represents an AST module which has not been linked yet
110#[derive(Clone)]
111pub struct PreLinkModule {
112 pub module: Box<Module>,
113 pub module_index: ModuleIndex,
114}
115
116/// Represents an assembled module or modules to use when resolving references while linking,
117/// as well as the method by which referenced symbols will be linked into the assembled MAST.
118#[derive(Clone)]
119pub struct LinkLibrary {
120 /// The library to link
121 pub library: Arc<Library>,
122 /// How to link against this library
123 pub kind: LinkLibraryKind,
124}
125
126impl LinkLibrary {
127 /// Dynamically link against `library`
128 pub fn dynamic(library: Arc<Library>) -> Self {
129 Self { library, kind: LinkLibraryKind::Dynamic }
130 }
131
132 /// Statically link `library`
133 pub fn r#static(library: Arc<Library>) -> Self {
134 Self { library, kind: LinkLibraryKind::Static }
135 }
136}
137
138/// Represents how a library should be linked into the assembled MAST
139#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
140pub enum LinkLibraryKind {
141 /// A dynamically-linked library.
142 ///
143 /// References to symbols of dynamically-linked libraries expect to have those symbols resolved
144 /// at runtime, i.e. it is expected that the library was loaded (or will be loaded on-demand),
145 /// and that the referenced symbol is resolvable by the VM.
146 ///
147 /// Concretely, the digest corresponding to a referenced procedure symbol will be linked as a
148 /// [`miden_core::mast::ExternalNode`], rather than including the procedure in the assembled
149 /// MAST, and referencing the procedure via [`miden_core::mast::MastNodeId`].
150 #[default]
151 Dynamic,
152 /// A statically-linked library.
153 ///
154 /// References to symbols of statically-linked libraries expect to be resolvable by the linker,
155 /// during assembly, i.e. it is expected that the library was provided to the assembler/linker
156 /// as an input, and that the entire definition of the referenced symbol is available.
157 ///
158 /// Concretely, a statically linked procedure will have its root, and all reachable nodes found
159 /// in the MAST of the library, included in the assembled MAST, and referenced via
160 /// [`miden_core::mast::MastNodeId`].
161 ///
162 /// Statically linked symbols are thus merged into the assembled artifact as if they had been
163 /// defined in your own project, and the library they were originally defined in will not be
164 /// required to be provided at runtime, as is the case with dynamically-linked libraries.
165 Static,
166}
167
168// LINKER
169// ================================================================================================
170
171/// The [`Linker`] is responsible for analyzing the input modules and libraries provided to the
172/// assembler, and _linking_ them together.
173///
174/// The core conceptual data structure of the linker is the _module graph_, which is implemented
175/// by a vector of module nodes, and a _call graph_, which is implemented as an adjacency matrix
176/// of procedure nodes and the outgoing edges from those nodes, representing references from that
177/// procedure to another symbol (typically as the result of procedure invocation, hence "call"
178/// graph).
179///
180/// Each procedure known to the linker is given a _global procedure index_, which is actually a
181/// pair of indices: a _module index_ (which indexes into the vector of module nodes), and a
182/// _procedure index_ (which indexes into the set of procedures defined by a module). These global
183/// procedure indices function as a unique identifier within the linker, to a specific procedure,
184/// and can be resolved to either the procedure AST, or to metadata about the procedure MAST.
185///
186/// The process of linking involves two phases:
187///
188/// 1. Setting up the linker context, by providing the set of libraries and/or input modules to link
189/// 2. Analyzing and rewriting the module graph, as needed, to ensure that all procedure references
190/// are resolved to either a concrete definition, or a "phantom" reference in the form of a MAST
191/// root.
192///
193/// The assembler will call [`Self::link`] once it has provided all inputs that it wants to link,
194/// which will, when successful, return the set of module indices corresponding to the modules that
195/// comprise the public interface of the assembled artifact. The assembler then constructs the MAST
196/// starting from the exported procedures of those modules, recursively tracing the call graph
197/// based on whether or not the callee is statically or dynamically linked. In the static linking
198/// case, any procedures referenced in a statically-linked library or module will be included in
199/// the assembled artifact. In the dynamic linking case, referenced procedures are instead
200/// referenced in the assembled artifact only by their MAST root.
201#[derive(Clone)]
202pub struct Linker {
203 /// The set of libraries to link against.
204 libraries: BTreeMap<Word, LinkLibrary>,
205 /// The nodes of the module graph data structure maintained by the linker.
206 modules: Vec<Option<ModuleLink>>,
207 /// The set of modules pending additional processing before adding them to the graph.
208 ///
209 /// When adding a set of inter-dependent modules to the graph, we process them as a group, so
210 /// that any references between them can be resolved, and the contents of the module
211 /// rewritten to reflect the changes.
212 ///
213 /// Once added to the graph, modules become immutable, and any additional modules added after
214 /// that must by definition only depend on modules in the graph, and not be depended upon.
215 pending: Vec<PreLinkModule>,
216 /// The global call graph of calls, not counting those that are performed directly via MAST
217 /// root.
218 callgraph: CallGraph,
219 /// The set of MAST roots which have procedure definitions in this graph. There can be
220 /// multiple procedures bound to the same root due to having identical code.
221 procedures_by_mast_root: BTreeMap<Word, SmallVec<[GlobalProcedureIndex; 1]>>,
222 /// The index of the kernel module in `modules`, if present
223 kernel_index: Option<ModuleIndex>,
224 /// The kernel library being linked against.
225 ///
226 /// This is always provided, with an empty kernel being the default.
227 kernel: Kernel,
228 /// The source manager to use when emitting diagnostics.
229 source_manager: Arc<dyn SourceManager>,
230}
231
232// ------------------------------------------------------------------------------------------------
233/// Constructors
234impl Linker {
235 /// Instantiate a new [Linker], using the provided [SourceManager] to resolve source info.
236 pub fn new(source_manager: Arc<dyn SourceManager>) -> Self {
237 Self {
238 libraries: Default::default(),
239 modules: Default::default(),
240 pending: Default::default(),
241 callgraph: Default::default(),
242 procedures_by_mast_root: Default::default(),
243 kernel_index: None,
244 kernel: Default::default(),
245 source_manager,
246 }
247 }
248
249 /// Registers `library` and all of its modules with the linker, according to its kind
250 pub fn link_library(&mut self, library: LinkLibrary) -> Result<(), LinkerError> {
251 use alloc::collections::btree_map::Entry;
252
253 match self.libraries.entry(*library.library.digest()) {
254 Entry::Vacant(entry) => {
255 entry.insert(library.clone());
256 self.link_assembled_modules(library.library.module_infos())
257 },
258 Entry::Occupied(mut entry) => {
259 let prev = entry.get_mut();
260
261 // If the same library is linked both dynamically and statically, prefer static
262 // linking always.
263 if matches!(prev.kind, LinkLibraryKind::Dynamic) {
264 prev.kind = library.kind;
265 }
266
267 Ok(())
268 },
269 }
270 }
271
272 /// Registers a set of MAST modules with the linker.
273 ///
274 /// If called directly, the modules will default to being dynamically linked. You must use
275 /// [`Self::link_library`] if you wish to statically link a set of assembled modules.
276 pub fn link_assembled_modules(
277 &mut self,
278 modules: impl IntoIterator<Item = ModuleInfo>,
279 ) -> Result<(), LinkerError> {
280 for module in modules {
281 self.link_assembled_module(module)?;
282 }
283
284 Ok(())
285 }
286
287 /// Registers a MAST module with the linker.
288 ///
289 /// If called directly, the module will default to being dynamically linked. You must use
290 /// [`Self::link_library`] if you wish to statically link `module`.
291 pub fn link_assembled_module(
292 &mut self,
293 module: ModuleInfo,
294 ) -> Result<ModuleIndex, LinkerError> {
295 log::debug!(target: "linker", "adding pre-assembled module {} to module graph", module.path());
296
297 let module_path = module.path();
298 let is_duplicate =
299 self.is_pending(module_path) || self.find_module_index(module_path).is_some();
300 if is_duplicate {
301 return Err(LinkerError::DuplicateModule { path: module_path.clone() });
302 }
303
304 let module_index = self.next_module_id();
305 for (proc_index, proc) in module.procedures() {
306 let gid = module_index + proc_index;
307 self.register_procedure_root(gid, proc.digest)?;
308 self.callgraph.get_or_insert_node(gid);
309 }
310 self.modules.push(Some(ModuleLink::Info(module)));
311 Ok(module_index)
312 }
313
314 /// Registers a set of AST modules with the linker.
315 ///
316 /// See [`Self::link_module`] for more details.
317 pub fn link_modules(
318 &mut self,
319 modules: impl IntoIterator<Item = Box<Module>>,
320 ) -> Result<Vec<ModuleIndex>, LinkerError> {
321 modules.into_iter().map(|m| self.link_module(m)).collect()
322 }
323
324 /// Registers an AST module with the linker.
325 ///
326 /// A module provided to this method is presumed to be dynamically linked, unless specifically
327 /// handled otherwise by the assembler. In particular, the assembler will only statically link
328 /// the set of AST modules provided to [`Self::link`], as they are expected to comprise the
329 /// public interface of the assembled artifact.
330 ///
331 /// # Errors
332 ///
333 /// This operation can fail for the following reasons:
334 ///
335 /// * Module with same [LibraryPath] is in the graph already
336 /// * Too many modules in the graph
337 ///
338 /// # Panics
339 ///
340 /// This function will panic if the number of modules exceeds the maximum representable
341 /// [ModuleIndex] value, `u16::MAX`.
342 pub fn link_module(&mut self, module: Box<Module>) -> Result<ModuleIndex, LinkerError> {
343 log::debug!(target: "linker", "adding unprocessed module {}", module.path());
344 let module_path = module.path();
345
346 let is_duplicate =
347 self.is_pending(module_path) || self.find_module_index(module_path).is_some();
348 if is_duplicate {
349 return Err(LinkerError::DuplicateModule { path: module_path.clone() });
350 }
351
352 let module_index = self.next_module_id();
353 self.modules.push(None);
354 self.pending.push(PreLinkModule { module, module_index });
355 Ok(module_index)
356 }
357
358 fn is_pending(&self, path: &LibraryPath) -> bool {
359 self.pending.iter().any(|m| m.module.path() == path)
360 }
361
362 #[inline]
363 fn next_module_id(&self) -> ModuleIndex {
364 ModuleIndex::new(self.modules.len())
365 }
366}
367
368// ------------------------------------------------------------------------------------------------
369/// Kernels
370impl Linker {
371 /// Returns a new [Linker] instantiated from the provided kernel and kernel info module.
372 ///
373 /// Note: it is assumed that kernel and kernel_module are consistent, but this is not checked.
374 ///
375 /// TODO: consider passing `KerneLibrary` into this constructor as a parameter instead.
376 pub(super) fn with_kernel(
377 source_manager: Arc<dyn SourceManager>,
378 kernel: Kernel,
379 kernel_module: ModuleInfo,
380 ) -> Self {
381 assert!(!kernel.is_empty());
382 assert_eq!(kernel_module.path(), &LibraryPath::from(LibraryNamespace::Kernel));
383 log::debug!(target: "linker", "instantiating linker with kernel {}", kernel_module.path());
384
385 let mut graph = Self::new(source_manager);
386 let kernel_index = graph
387 .link_assembled_module(kernel_module)
388 .expect("failed to add kernel module to the module graph");
389
390 graph.kernel_index = Some(kernel_index);
391 graph.kernel = kernel;
392 graph
393 }
394
395 pub fn kernel(&self) -> &Kernel {
396 &self.kernel
397 }
398
399 pub fn has_nonempty_kernel(&self) -> bool {
400 self.kernel_index.is_some() || !self.kernel.is_empty()
401 }
402}
403
404// ------------------------------------------------------------------------------------------------
405/// Analysis
406impl Linker {
407 /// Links `modules` using the current state of the linker.
408 ///
409 /// Returns the module indices corresponding to the provided modules, which are expected to
410 /// provide the public interface of the final assembled artifact.
411 pub fn link(
412 &mut self,
413 modules: impl IntoIterator<Item = Box<Module>>,
414 ) -> Result<Vec<ModuleIndex>, LinkerError> {
415 let module_indices = self.link_modules(modules)?;
416
417 self.link_and_rewrite()?;
418
419 Ok(module_indices)
420 }
421
422 /// Links `kernel` using the current state of the linker.
423 ///
424 /// Returns the module index of the kernel module, which is expected to provide the public
425 /// interface of the final assembled kernel.
426 ///
427 /// This differs from `link` in that we allow all AST modules in the module graph access to
428 /// kernel features, e.g. `caller`, as if they are defined by the kernel module itself.
429 pub fn link_kernel(&mut self, kernel: Box<Module>) -> Result<Vec<ModuleIndex>, LinkerError> {
430 let module_index = self.link_module(kernel)?;
431
432 // Set the module kind of all pending AST modules to Kernel, as we are linking a kernel
433 for module in self.pending.iter_mut() {
434 module.module.set_kind(crate::ast::ModuleKind::Kernel);
435 }
436
437 self.link_and_rewrite()?;
438
439 Ok(vec![module_index])
440 }
441
442 /// Compute the module graph from the set of pending modules, and link it, rewriting any AST
443 /// modules with unresolved, or partially-resolved, symbol references.
444 ///
445 /// This should be called any time you add more libraries or modules to the module graph, to
446 /// ensure that the graph is valid, and that there are no unresolved references. In general,
447 /// you will only instantiate the linker, build up the graph, and link a single time; but you
448 /// can re-use the linker to build multiple artifacts as well.
449 ///
450 /// When this function is called, some initial information is calculated about the AST modules
451 /// which are to be added to the graph, and then each module is visited to perform a deeper
452 /// analysis than can be done by the `sema` module, as we now have the full set of modules
453 /// available to do import resolution, and to rewrite invoke targets with their absolute paths
454 /// and/or MAST roots. A variety of issues are caught at this stage.
455 ///
456 /// Once each module is validated, the various analysis results stored as part of the graph
457 /// structure are updated to reflect that module being added to the graph. Once part of the
458 /// graph, the module becomes immutable/clone-on-write, so as to allow the graph to be
459 /// cheaply cloned.
460 ///
461 /// The final, and most important, analysis done by this function is the topological sort of
462 /// the global call graph, which contains the inter-procedural dependencies of every procedure
463 /// in the module graph. We use this sort order to do two things:
464 ///
465 /// 1. Verify that there are no static cycles in the graph that would prevent us from being able
466 /// to hash the generated MAST of the program. NOTE: dynamic cycles, e.g. those induced by
467 /// `dynexec`, are perfectly fine, we are only interested in preventing cycles that interfere
468 /// with the ability to generate MAST roots.
469 ///
470 /// 2. Visit the call graph bottom-up, so that we can fully compile a procedure before any of
471 /// its callers, and thus rewrite those callers to reference that procedure by MAST root,
472 /// rather than by name. As a result, a compiled MAST program is like an immutable snapshot
473 /// of the entire call graph at the time of compilation. Later, if we choose to recompile a
474 /// subset of modules (currently we do not have support for this in the assembler API), we
475 /// can re-analyze/re-compile only those parts of the graph which have actually changed.
476 ///
477 /// NOTE: This will return `Err` if we detect a validation error, a cycle in the graph, or an
478 /// operation not supported by the current configuration. Basically, for any reason that would
479 /// cause the resulting graph to represent an invalid program.
480 fn link_and_rewrite(&mut self) -> Result<(), LinkerError> {
481 log::debug!(target: "linker", "processing {} new modules, and recomputing module graph", self.pending.len());
482
483 // It is acceptable for there to be no changes, but if the graph is empty and no changes
484 // are being made, we treat that as an error
485 if self.modules.is_empty() && self.pending.is_empty() {
486 return Err(LinkerError::Empty);
487 }
488
489 // If no changes are being made, we're done
490 if self.pending.is_empty() {
491 return Ok(());
492 }
493
494 // Visit all of the pending modules, assigning them ids, and adding them to the module
495 // graph after rewriting any calls to use absolute paths
496 let high_water_mark = self.modules.len();
497 let pending = core::mem::take(&mut self.pending);
498 for PreLinkModule { module: pending_module, module_index } in pending.iter() {
499 log::debug!(
500 target: "linker",
501 "adding procedures from pending module {} (index {}) to call graph",
502 pending_module.path(),
503 module_index.as_usize()
504 );
505
506 // Apply module to call graph
507 for (index, _) in pending_module.procedures().enumerate() {
508 let procedure_id = ProcedureIndex::new(index);
509 let global_id = GlobalProcedureIndex {
510 module: *module_index,
511 index: procedure_id,
512 };
513
514 // Ensure all entrypoints and exported symbols are represented in the call
515 // graph, even if they have no edges, we need them
516 // in the graph for the topological sort
517 self.callgraph.get_or_insert_node(global_id);
518 }
519 }
520
521 // Obtain a set of resolvers for the pending modules so that we can do name resolution
522 // before they are added to the graph
523 let mut resolver = NameResolver::new(self);
524 for module in pending.iter() {
525 resolver.push_pending(module);
526 }
527 let mut edges = Vec::new();
528 let mut finished: Vec<PreLinkModule> = Vec::with_capacity(pending.len());
529
530 // Visit all of the newly-added modules and perform any rewrites to AST modules.
531 for PreLinkModule { mut module, module_index } in pending.into_iter() {
532 log::debug!(target: "linker", "rewriting pending module {} (index {})", module.path(), module_index.as_usize());
533
534 let mut rewriter = ModuleRewriter::new(&resolver);
535 rewriter.apply(module_index, &mut module)?;
536
537 log::debug!(
538 target: "linker",
539 "processing procedures of pending module {} (index {})",
540 module.path(),
541 module_index.as_usize()
542 );
543 for (index, procedure) in module.procedures().enumerate() {
544 log::debug!(target: "linker", " * processing {} at index {index}", procedure.name());
545
546 let procedure_id = ProcedureIndex::new(index);
547 let gid = GlobalProcedureIndex {
548 module: module_index,
549 index: procedure_id,
550 };
551
552 // Add edge to the call graph to represent dependency on aliased procedures
553 if let Export::Alias(alias) = procedure {
554 log::debug!(target: "linker", " | resolving alias {}..", alias.target());
555
556 let caller = CallerInfo {
557 span: alias.span(),
558 module: module_index,
559 kind: InvokeKind::ProcRef,
560 };
561 let target = alias.target().into();
562 if let Some(callee) =
563 resolver.resolve_target(&caller, &target)?.into_global_id()
564 {
565 log::debug!(
566 target: "linker",
567 " | resolved alias to gid {:?}:{:?}",
568 callee.module,
569 callee.index
570 );
571 edges.push((gid, callee));
572 }
573 }
574
575 // Add edges to all transitive dependencies of this procedure due to calls
576 for invoke in procedure.invoked() {
577 log::debug!(target: "linker", " | recording {} dependency on {}", invoke.kind, &invoke.target);
578
579 let caller = CallerInfo {
580 span: invoke.span(),
581 module: module_index,
582 kind: invoke.kind,
583 };
584 if let Some(callee) =
585 resolver.resolve_target(&caller, &invoke.target)?.into_global_id()
586 {
587 log::debug!(
588 target: "linker",
589 " | resolved dependency to gid {}:{}",
590 callee.module.as_usize(),
591 callee.index.as_usize()
592 );
593 edges.push((gid, callee));
594 }
595 }
596 }
597
598 finished.push(PreLinkModule { module, module_index });
599 }
600
601 // Release the graph again
602 drop(resolver);
603
604 // Update the graph with the processed modules
605 for PreLinkModule { module, module_index } in finished {
606 self.modules[module_index.as_usize()] = Some(ModuleLink::Ast(Arc::from(module)));
607 }
608
609 edges
610 .into_iter()
611 .for_each(|(caller, callee)| self.callgraph.add_edge(caller, callee));
612
613 // Visit all of the (AST) modules in the base module graph, and modify them if any of the
614 // pending modules allow additional information to be inferred (such as the absolute path of
615 // imports, etc)
616 for module_index in 0..high_water_mark {
617 let module_index = ModuleIndex::new(module_index);
618 let module = self.modules[module_index.as_usize()].clone().unwrap_or_else(|| {
619 panic!(
620 "expected module at index {} to have been processed, but it is None",
621 module_index.as_usize()
622 )
623 });
624
625 match module {
626 ModuleLink::Ast(module) => {
627 log::debug!(target: "linker", "re-analyzing module {} (index {})", module.path(), module_index.as_usize());
628 // Re-analyze the module, and if we needed to clone-on-write, the new module
629 // will be returned. Otherwise, `Ok(None)` indicates that
630 // the module is unchanged, and `Err` indicates that
631 // re-analysis has found an issue with this module.
632 let new_module =
633 self.reanalyze_module(module_index, module).map(ModuleLink::Ast)?;
634 self.modules[module_index.as_usize()] = Some(new_module);
635 },
636 module => {
637 self.modules[module_index.as_usize()] = Some(module);
638 },
639 }
640 }
641
642 // Make sure the graph is free of cycles
643 self.callgraph.toposort().map_err(|cycle| {
644 let iter = cycle.into_node_ids();
645 let mut nodes = Vec::with_capacity(iter.len());
646 for node in iter {
647 let module = self[node.module].path();
648 let proc = self.get_procedure_unsafe(node);
649 nodes.push(format!("{}::{}", module, proc.name()));
650 }
651 LinkerError::Cycle { nodes: nodes.into() }
652 })?;
653
654 Ok(())
655 }
656
657 fn reanalyze_module(
658 &mut self,
659 module_id: ModuleIndex,
660 module: Arc<Module>,
661 ) -> Result<Arc<Module>, LinkerError> {
662 let resolver = NameResolver::new(self);
663 let maybe_rewrite = MaybeRewriteCheck::new(&resolver);
664 if maybe_rewrite.check(module_id, &module)? {
665 // We need to rewrite this module again, so get an owned copy of the original
666 // and use that
667 let mut module = Box::new(Arc::unwrap_or_clone(module));
668 let mut rewriter = ModuleRewriter::new(&resolver);
669 rewriter.apply(module_id, &mut module)?;
670
671 Ok(Arc::from(module))
672 } else {
673 Ok(module)
674 }
675 }
676}
677
678// ------------------------------------------------------------------------------------------------
679/// Accessors/Queries
680impl Linker {
681 /// Get an iterator over the external libraries the linker has linked against
682 pub fn libraries(&self) -> impl Iterator<Item = &LinkLibrary> {
683 self.libraries.values()
684 }
685
686 /// Compute the topological sort of the callgraph rooted at `caller`
687 pub fn topological_sort_from_root(
688 &self,
689 caller: GlobalProcedureIndex,
690 ) -> Result<Vec<GlobalProcedureIndex>, CycleError> {
691 self.callgraph.toposort_caller(caller)
692 }
693
694 /// Fetch a [ProcedureLink] by its [GlobalProcedureIndex].
695 ///
696 /// # Panics
697 /// - Panics if index is invalid.
698 pub fn get_procedure_unsafe(&self, id: GlobalProcedureIndex) -> ProcedureLink<'_> {
699 match self.modules[id.module.as_usize()]
700 .as_ref()
701 .expect("invalid reference to pending module")
702 {
703 ModuleLink::Ast(m) => ProcedureLink::Ast(&m[id.index]),
704 ModuleLink::Info(m) => ProcedureLink::Info(m.get_procedure_by_index(id.index).unwrap()),
705 }
706 }
707
708 /// Returns a procedure index which corresponds to the provided procedure digest.
709 ///
710 /// Note that there can be many procedures with the same digest - due to having the same code,
711 /// and/or using different decorators which don't affect the MAST root. This method returns an
712 /// arbitrary one.
713 pub fn get_procedure_index_by_digest(
714 &self,
715 procedure_digest: &Word,
716 ) -> Option<GlobalProcedureIndex> {
717 self.procedures_by_mast_root.get(procedure_digest).map(|indices| indices[0])
718 }
719
720 /// Resolves `target` from the perspective of `caller`.
721 pub fn resolve_target(
722 &self,
723 caller: &CallerInfo,
724 target: &InvocationTarget,
725 ) -> Result<ResolvedTarget, LinkerError> {
726 let resolver = NameResolver::new(self);
727 resolver.resolve_target(caller, target)
728 }
729
730 /// Registers a [MastNodeId] as corresponding to a given [GlobalProcedureIndex].
731 ///
732 /// # SAFETY
733 ///
734 /// It is essential that the caller _guarantee_ that the given digest belongs to the specified
735 /// procedure. It is fine if there are multiple procedures with the same digest, but it _must_
736 /// be the case that if a given digest is specified, it can be used as if it was the definition
737 /// of the referenced procedure, i.e. they are referentially transparent.
738 pub(crate) fn register_procedure_root(
739 &mut self,
740 id: GlobalProcedureIndex,
741 procedure_mast_root: Word,
742 ) -> Result<(), LinkerError> {
743 use alloc::collections::btree_map::Entry;
744 match self.procedures_by_mast_root.entry(procedure_mast_root) {
745 Entry::Occupied(ref mut entry) => {
746 let prev_id = entry.get()[0];
747 if prev_id != id {
748 // Multiple procedures with the same root, but compatible
749 entry.get_mut().push(id);
750 }
751 },
752 Entry::Vacant(entry) => {
753 entry.insert(smallvec![id]);
754 },
755 }
756
757 Ok(())
758 }
759
760 /// Resolve a [LibraryPath] to a [ModuleIndex] in this graph
761 pub fn find_module_index(&self, name: &LibraryPath) -> Option<ModuleIndex> {
762 self.modules
763 .iter()
764 .position(|m| m.as_ref().is_some_and(|m| m.path() == name))
765 .map(ModuleIndex::new)
766 }
767
768 /// Resolve a [LibraryPath] to a [Module] in this graph
769 pub fn find_module(&self, name: &LibraryPath) -> Option<ModuleLink> {
770 self.modules
771 .iter()
772 .find(|m| m.as_ref().is_some_and(|m| m.path() == name))
773 .cloned()
774 .unwrap_or(None)
775 }
776}
777
778impl Index<ModuleIndex> for Linker {
779 type Output = ModuleLink;
780
781 fn index(&self, index: ModuleIndex) -> &Self::Output {
782 self.modules
783 .index(index.as_usize())
784 .as_ref()
785 .expect("invalid reference to pending module")
786 }
787}