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