1mod callgraph;
39mod debug;
40mod errors;
41mod library;
42mod module;
43pub mod namespaces;
44mod resolver;
45mod rewrites;
46mod symbols;
47
48use alloc::{
49 boxed::Box,
50 collections::{BTreeMap, BTreeSet},
51 string::{String, ToString},
52 sync::Arc,
53 vec::Vec,
54};
55use core::{
56 cell::RefCell,
57 ops::{ControlFlow, Index},
58};
59
60use miden_assembly_syntax::{
61 Report,
62 ast::{
63 self, AttributeSet, GlobalItemIndex, InvocationTarget, ItemIndex, Module, ModuleIndex,
64 Path, SymbolResolution, Visibility, types,
65 },
66 debuginfo::{SourceManager, SourceSpan, Span, Spanned},
67 module::{ItemInfo, ModuleDescriptor},
68};
69use miden_core::{Word, advice::AdviceMap, program::KernelDescriptor};
70use miden_mast_package::Package as MastPackage;
71use smallvec::{SmallVec, smallvec};
72
73pub use self::{
74 callgraph::{CallGraph, CycleError},
75 errors::LinkerError,
76 library::{LinkLibrary, Linkage},
77 namespaces::NamespaceGraph,
78 resolver::{ResolverCache, SymbolResolutionContext, SymbolResolver},
79 symbols::{Import, Symbol, SymbolItem},
80};
81use self::{
82 module::{LinkModule, ModuleSource},
83 namespaces::ResolvedImports,
84 resolver::*,
85};
86
87#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
95pub enum LinkMode {
96 #[default]
100 Strict,
101 Analysis,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
115pub struct LinkAnalysis {
116 pub module_indices: Vec<ModuleIndex>,
119 pub cycle: Box<[String]>,
122}
123
124impl LinkAnalysis {
125 pub fn has_cycle(&self) -> bool {
127 !self.cycle.is_empty()
128 }
129}
130
131#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
133pub enum LinkStatus {
134 #[default]
136 Unlinked,
137 PartiallyLinked,
140 Linked,
142}
143
144#[derive(Clone)]
177pub struct Linker {
178 libraries: BTreeMap<Word, LinkLibrary>,
180 static_libraries: BTreeMap<Word, LinkLibrary>,
186 modules: Vec<LinkModule>,
188 callgraph: CallGraph,
191 procedures_by_mast_root: BTreeMap<Word, SmallVec<[GlobalItemIndex; 1]>>,
194 kernel_index: Option<ModuleIndex>,
196 kernel: KernelDescriptor,
200 kernel_package: Option<Arc<MastPackage>>,
201 source_manager: Arc<dyn SourceManager>,
203}
204
205impl Linker {
208 pub fn new(source_manager: Arc<dyn SourceManager>) -> Self {
210 Self {
211 libraries: Default::default(),
212 static_libraries: Default::default(),
213 modules: Default::default(),
214 callgraph: Default::default(),
215 procedures_by_mast_root: Default::default(),
216 kernel_index: None,
217 kernel: Default::default(),
218 kernel_package: None,
219 source_manager,
220 }
221 }
222
223 pub fn link_library(&mut self, library: LinkLibrary) -> Result<(), LinkerError> {
225 use alloc::collections::btree_map::Entry;
226
227 let module_descriptors = library.module_descriptors().map_err(|err| {
228 LinkerError::InvalidPackageModuleSurface {
229 package: library.package.name.to_string(),
230 reason: err.to_string(),
231 }
232 })?;
233 let library_interface_digest = library.interface_commitment().map_err(|err| {
234 LinkerError::InvalidPackageModuleSurface {
235 package: library.package.name.to_string(),
236 reason: err.to_string(),
237 }
238 })?;
239
240 let static_library = matches!(library.linkage, Linkage::Static).then(|| library.clone());
241 let result = match self.libraries.entry(library_interface_digest) {
242 Entry::Vacant(entry) => {
243 entry.insert(library);
244 self.link_assembled_modules(module_descriptors)
245 },
246 Entry::Occupied(mut entry) => {
247 let prev = entry.get_mut();
248
249 if matches!(prev.linkage, Linkage::Dynamic) {
252 prev.linkage = library.linkage;
253 }
254
255 Ok(())
256 },
257 };
258
259 if result.is_ok()
260 && let Some(static_library) = static_library
261 {
262 self.static_libraries
263 .entry(static_library.commitment())
264 .or_insert(static_library);
265 }
266
267 result
268 }
269
270 pub fn link_assembled_modules(
275 &mut self,
276 modules: impl IntoIterator<Item = ModuleDescriptor>,
277 ) -> Result<(), LinkerError> {
278 for module in modules {
279 self.link_assembled_module(module)?;
280 }
281
282 Ok(())
283 }
284
285 pub fn link_assembled_module(
290 &mut self,
291 module: ModuleDescriptor,
292 ) -> Result<ModuleIndex, LinkerError> {
293 log::debug!(target: "linker", "adding pre-assembled module {} to module graph", module.path());
294
295 let module_path = module.path();
296 let is_duplicate = self.find_module_index(module_path).is_some();
297 if is_duplicate {
298 return Err(LinkerError::DuplicateModule {
299 path: module_path.to_path_buf().into_boxed_path().into(),
300 });
301 }
302
303 let module_index = self.next_module_id();
304 let submodules = module.submodules().to_vec();
305 let items = module.items();
306 let mut symbols = Vec::with_capacity(items.len());
307 for (idx, item) in items {
308 let gid = module_index + idx;
309 self.callgraph.get_or_insert_node(gid);
310 match &item {
311 ItemInfo::Procedure(item) => {
312 self.register_procedure_root(gid, item.digest);
313 },
314 ItemInfo::Constant(_) | ItemInfo::Type(_) => (),
315 }
316 symbols.push(Symbol::new(
317 item.name().clone(),
318 Visibility::Public,
319 LinkStatus::Linked,
320 SymbolItem::Compiled(item.clone()),
321 ));
322 }
323
324 let link_module = LinkModule::new(
325 module_index,
326 ast::ModuleKind::Library,
327 LinkStatus::Linked,
328 ModuleSource::Mast,
329 module_path.into(),
330 )
331 .with_submodules(submodules)
332 .with_symbols(symbols);
333
334 self.modules.push(link_module);
335 Ok(module_index)
336 }
337
338 pub fn link_modules(
342 &mut self,
343 modules: impl IntoIterator<Item = Box<Module>>,
344 ) -> Result<Vec<ModuleIndex>, LinkerError> {
345 modules.into_iter().map(|mut m| self.link_module(&mut m)).collect()
346 }
347
348 pub fn link_module(&mut self, module: &mut Module) -> Result<ModuleIndex, LinkerError> {
367 log::debug!(target: "linker", "adding unprocessed module {}", module.path());
368
369 let is_duplicate = self.find_module_index(module.path()).is_some();
370 if is_duplicate {
371 return Err(LinkerError::DuplicateModule { path: module.path().into() });
372 }
373
374 let module_index = self.next_module_id();
375 let submodules = module.submodules().to_vec();
376 let mut symbols = Vec::new();
377 let imports = module.take_imports().into_iter().map(Import::new).collect::<Vec<_>>();
378 for item in module.take_items() {
379 match item {
380 ast::Item::Type(item) => {
381 let gid = module_index + ItemIndex::new(symbols.len());
382 self.callgraph.get_or_insert_node(gid);
383 symbols.push(Symbol::new(
384 item.name().clone(),
385 item.visibility(),
386 LinkStatus::Unlinked,
387 SymbolItem::Type(item),
388 ));
389 },
390 ast::Item::Constant(item) => {
391 let gid = module_index + ItemIndex::new(symbols.len());
392 self.callgraph.get_or_insert_node(gid);
393 symbols.push(Symbol::new(
394 item.name().clone(),
395 item.visibility,
396 LinkStatus::Unlinked,
397 SymbolItem::Constant(item),
398 ));
399 },
400 ast::Item::Procedure(item) => {
401 let gid = module_index + ItemIndex::new(symbols.len());
402 self.callgraph.get_or_insert_node(gid);
403 symbols.push(Symbol::new(
404 item.name().clone().into(),
405 item.visibility(),
406 LinkStatus::Unlinked,
407 SymbolItem::Procedure(RefCell::new(Box::new(item))),
408 ));
409 },
410 }
411 }
412 let link_module = LinkModule::new(
413 module_index,
414 module.kind(),
415 LinkStatus::Unlinked,
416 ModuleSource::Ast,
417 module.path().into(),
418 )
419 .with_advice_map(module.advice_map().clone())
420 .with_submodules(submodules)
421 .with_imports(imports)
422 .with_symbols(symbols);
423
424 self.modules.push(link_module);
425 Ok(module_index)
426 }
427
428 #[inline]
429 fn next_module_id(&self) -> ModuleIndex {
430 ModuleIndex::new(self.modules.len())
431 }
432}
433
434impl Linker {
437 pub fn with_kernel(
441 source_manager: Arc<dyn SourceManager>,
442 kernel_package: Arc<MastPackage>,
443 ) -> Result<Self, Report> {
444 log::debug!(target: "linker", "instantiating linker with kernel package {}@{}", kernel_package.name, kernel_package.version);
445
446 let mut linker = Self::new(source_manager);
447 linker.link_with_kernel(kernel_package)?;
448
449 Ok(linker)
450 }
451
452 pub fn link_with_kernel(&mut self, kernel_package: Arc<MastPackage>) -> Result<(), Report> {
460 if !kernel_package.is_kernel() {
461 return Err(Report::msg("invalid kernel package: not a kernel"));
462 }
463 let kernel = kernel_package.to_kernel_descriptor()?;
464 if kernel.is_empty() {
465 return Err(Report::msg("invalid kernel package: kernel cannot be empty"));
466 }
467 assert!(self.kernel.is_empty());
468 assert!(self.kernel_package.is_none());
469
470 log::debug!(target: "linker", "modifying linker with kernel package {}@{}", kernel_package.name, kernel_package.version);
471
472 let mut kernel_index = None;
473 let module_descriptors = kernel_package.try_module_descriptors().map_err(|err| {
474 LinkerError::InvalidPackageModuleSurface {
475 package: kernel_package.name.to_string(),
476 reason: err.to_string(),
477 }
478 })?;
479 for module_descriptor in module_descriptors {
480 let is_kernel_module = module_descriptor.path().is_kernel_path();
481 let module_index = self.link_assembled_module(module_descriptor)?;
482 if is_kernel_module {
483 kernel_index = Some(module_index);
484 }
485 }
486 assert!(kernel_index.is_some());
487
488 self.kernel_index = kernel_index;
489 self.kernel = kernel;
490 self.kernel_package = Some(kernel_package);
491
492 Ok(())
493 }
494
495 pub fn kernel(&self) -> &KernelDescriptor {
496 &self.kernel
497 }
498
499 pub fn kernel_package(&self) -> Option<Arc<MastPackage>> {
500 self.kernel_package.clone()
501 }
502
503 pub fn has_nonempty_kernel(&self) -> bool {
504 self.kernel_index.is_some() || !self.kernel.is_empty()
505 }
506}
507
508impl Linker {
511 fn cycle_error(&self, cycle: CycleError) -> LinkerError {
512 LinkerError::Cycle { nodes: self.cycle_procedure_paths(cycle) }
513 }
514
515 fn cycle_procedure_paths(&self, cycle: CycleError) -> Box<[String]> {
517 cycle
518 .into_node_ids()
519 .map(|node| {
520 let module = self[node.module].path();
521 let item = self[node].name();
522 module.join(item).to_string()
523 })
524 .collect::<Vec<String>>()
525 .into_boxed_slice()
526 }
527
528 pub fn link(
539 &mut self,
540 roots: impl IntoIterator<Item = Box<Module>>,
541 support: impl IntoIterator<Item = Box<Module>>,
542 ) -> Result<Vec<ModuleIndex>, LinkerError> {
543 use alloc::collections::BTreeSet;
544
545 let root_indices = self.link_modules(roots)?;
546 let _support_indices = self.link_modules(support)?;
547 let namespaces = NamespaceGraph::build(self)?;
548 let imports = namespaces.resolve_imports(self)?;
549
550 self.link_and_rewrite(&namespaces, &imports, LinkMode::Strict)?;
551
552 let mut reachable = BTreeSet::new();
553
554 for root in root_indices {
555 reachable.extend(namespaces.reachable_from_root(root));
556 }
557
558 Ok(reachable.into_iter().collect())
559 }
560
561 pub fn link_analysis(
571 &mut self,
572 roots: impl IntoIterator<Item = Box<Module>>,
573 support: impl IntoIterator<Item = Box<Module>>,
574 ) -> Result<LinkAnalysis, LinkerError> {
575 use alloc::collections::BTreeSet;
576
577 let root_indices = self.link_modules(roots)?;
578 let _support_indices = self.link_modules(support)?;
579 let namespaces = NamespaceGraph::build(self)?;
580 let imports = namespaces.resolve_imports(self)?;
581
582 let cycle = self.link_and_rewrite(&namespaces, &imports, LinkMode::Analysis)?;
583
584 let module_indices = {
585 let mut reachable = BTreeSet::new();
586 for root in root_indices {
587 reachable.extend(namespaces.reachable_from_root(root));
588 }
589 reachable.into_iter().collect::<Vec<_>>()
590 };
591
592 let cycle = match cycle {
593 Some(cycle) => self.cycle_procedure_paths(cycle),
594 None => Box::new([]),
595 };
596
597 Ok(LinkAnalysis { module_indices, cycle })
598 }
599
600 pub fn link_kernel(
608 &mut self,
609 mut kernel: Box<Module>,
610 support: impl IntoIterator<Item = Box<Module>>,
611 ) -> Result<Vec<ModuleIndex>, LinkerError> {
612 self.link_modules(support)?;
613 let original_module_len = self.modules.len();
614 let original_callgraph = self.callgraph.clone();
615 let module_index = self.link_module(&mut kernel)?;
616 let original_kernel_index = self.kernel_index;
617 let original_module_kinds = self
618 .modules
619 .iter()
620 .enumerate()
621 .take(module_index.as_usize())
622 .filter(|(_, module)| matches!(module.source(), ModuleSource::Ast))
623 .map(|(module_index, module)| (module_index, module.kind()))
624 .collect::<Vec<_>>();
625
626 for module in self.modules.iter_mut().take(module_index.as_usize()) {
628 if matches!(module.source(), ModuleSource::Ast) {
629 module.set_kind(ast::ModuleKind::Kernel);
630 }
631 }
632
633 self.kernel_index = Some(module_index);
634
635 let result = (|| {
636 let namespaces = NamespaceGraph::build(self)?;
637 let imports = namespaces.resolve_imports(self)?;
638 self.link_and_rewrite(&namespaces, &imports, LinkMode::Strict)?;
639
640 Ok(namespaces.reachable_from_root(module_index))
641 })();
642
643 match result {
644 ok @ Ok(_) => ok,
645 err => {
646 self.kernel_index = original_kernel_index;
647 self.callgraph = original_callgraph;
648 self.modules.truncate(original_module_len);
649 for (module_index, module_kind) in original_module_kinds {
650 self.modules[module_index].set_kind(module_kind);
651 }
652
653 err
654 },
655 }
656 }
657
658 fn link_and_rewrite(
698 &mut self,
699 namespaces: &NamespaceGraph,
700 imports: &ResolvedImports,
701 mode: LinkMode,
702 ) -> Result<Option<CycleError>, LinkerError> {
703 log::debug!(
704 target: "linker",
705 "processing {} unlinked/partially-linked modules, and recomputing module graph",
706 self.modules.iter().filter(|m| !m.is_linked()).count()
707 );
708
709 if self.modules.is_empty() {
712 return Err(LinkerError::Empty);
713 }
714
715 if self.modules.iter().all(LinkModule::is_linked) {
718 return match self.callgraph.toposort() {
719 Err(cycle) if mode == LinkMode::Strict => Err(self.cycle_error(cycle)),
720 Err(cycle) => Ok(Some(cycle)),
721 Ok(_) => Ok(None),
722 };
723 }
724
725 let pending_modules = self
728 .modules
729 .iter()
730 .enumerate()
731 .filter(|(_, module)| module.is_unlinked())
732 .map(|(module_index, module)| (module_index, module.clone()))
733 .collect::<Vec<_>>();
734 let original_callgraph = self.callgraph.clone();
735
736 let result = (|| {
737 let resolver = SymbolResolver::with_namespaces(self, namespaces, imports);
738 let mut edges = Vec::new();
739 let mut cache = ResolverCache::default();
740 let mut linked_modules = Vec::new();
741
742 for (module_index, module) in self.modules.iter().enumerate() {
743 if !module.is_unlinked() {
744 continue;
745 }
746
747 let module_index = ModuleIndex::new(module_index);
748
749 for import in module.imports() {
750 if let Some(namespaces::ResolvedUse::Item(gid)) =
751 imports.get(module_index, import.local_name().as_str())
752 {
753 import.set_resolved(gid);
754 }
755 }
756
757 for (symbol_idx, symbol) in module.symbols().enumerate() {
758 let gid = module_index + ItemIndex::new(symbol_idx);
759
760 rewrites::rewrite_symbol(gid, symbol, &resolver, &mut cache)?;
762
763 match symbol.item() {
765 SymbolItem::Compiled(_) | SymbolItem::Type(_) | SymbolItem::Constant(_) => {
766 },
767 SymbolItem::Procedure(proc) => {
768 let proc = proc.borrow();
771 for invoke in proc.invoked() {
772 log::debug!(target: "linker", " | recording {} dependency on {}", invoke.kind, invoke.target);
773
774 let context = SymbolResolutionContext {
775 span: invoke.span(),
776 module: module_index,
777 kind: Some(invoke.kind),
778 };
779 if let Some(callee) = resolver
780 .resolve_invoke_target(&context, &invoke.target)?
781 .into_global_id()
782 {
783 log::debug!(
784 target: "linker",
785 " | resolved dependency to gid {}:{}",
786 callee.module.as_usize(),
787 callee.index.as_usize()
788 );
789 edges.push((gid, callee));
790 }
791 }
792 },
793 }
794 }
795
796 linked_modules.push(module_index);
797 }
798
799 let mut callgraph = self.callgraph.clone();
800 let mut cycle_nodes: BTreeSet<GlobalItemIndex> = BTreeSet::new();
805 for (caller, callee) in edges {
806 match callgraph.add_edge(caller, callee) {
807 Ok(()) => (),
808 Err(cycle) => {
812 callgraph.get_or_insert_node(callee);
813 callgraph.get_or_insert_node(caller).push(callee);
814 cycle_nodes.extend(cycle.into_node_ids());
815 },
816 }
817 }
818
819 if let Err(cycle) = callgraph.toposort() {
821 cycle_nodes.extend(cycle.into_node_ids());
822 }
823
824 let cycle = if cycle_nodes.is_empty() {
825 None
826 } else {
827 Some(CycleError::new(cycle_nodes))
828 };
829
830 if mode == LinkMode::Strict
833 && let Some(cycle) = cycle
834 {
835 Err(self.cycle_error(cycle))
836 } else {
837 Ok::<_, LinkerError>((linked_modules, callgraph, cycle))
838 }
839 })();
840
841 match result {
842 Ok((linked_modules, callgraph, cycle)) => {
843 self.callgraph = callgraph;
844 for module_index in linked_modules {
845 self.modules[module_index.as_usize()].set_status(LinkStatus::Linked);
846 }
847 Ok(cycle)
848 },
849 Err(err) => {
850 self.callgraph = original_callgraph;
851 for (module_index, module) in pending_modules {
852 self.modules[module_index] = module;
853 }
854 Err(err)
855 },
856 }
857 }
858}
859
860impl Linker {
863 pub fn modules(&self) -> &[LinkModule] {
865 self.modules.as_slice()
866 }
867
868 pub fn libraries(&self) -> impl Iterator<Item = &LinkLibrary> {
870 self.libraries.values()
871 }
872
873 pub fn static_libraries(&self) -> impl Iterator<Item = &LinkLibrary> {
875 self.static_libraries.values()
876 }
877
878 pub fn topological_sort_from_root(
880 &self,
881 caller: GlobalItemIndex,
882 ) -> Result<Vec<GlobalItemIndex>, CycleError> {
883 self.callgraph.toposort_caller(caller)
884 }
885
886 pub fn get_procedure_index_by_digest(
891 &self,
892 procedure_digest: &Word,
893 ) -> Option<GlobalItemIndex> {
894 self.procedures_by_mast_root.get(procedure_digest).map(|indices| indices[0])
895 }
896
897 pub fn resolve_invoke_target(
899 &self,
900 caller: &SymbolResolutionContext,
901 target: &InvocationTarget,
902 ) -> Result<SymbolResolution, LinkerError> {
903 let namespaces = NamespaceGraph::build(self)?;
904 let imports = namespaces.resolve_imports(self)?;
905 let resolver = SymbolResolver::with_namespaces(self, &namespaces, &imports);
906 resolver.resolve_invoke_target(caller, target)
907 }
908
909 pub fn resolve_path(
911 &self,
912 caller: &SymbolResolutionContext,
913 path: &Path,
914 ) -> Result<SymbolResolution, LinkerError> {
915 let namespaces = NamespaceGraph::build(self)?;
916 let imports = namespaces.resolve_imports(self)?;
917 let resolver = SymbolResolver::with_namespaces(self, &namespaces, &imports);
918 resolver.resolve_path(caller, Span::new(caller.span, path))
919 }
920
921 pub fn resolve_signature(
923 &self,
924 gid: GlobalItemIndex,
925 ) -> Result<Option<Arc<types::FunctionType>>, LinkerError> {
926 match self[gid].item() {
927 SymbolItem::Compiled(ItemInfo::Procedure(proc)) => Ok(proc.signature.clone()),
928 SymbolItem::Procedure(proc) => {
929 let proc = proc.borrow();
930 match proc.signature() {
931 Some(ty) => self.translate_function_type(gid.module, ty).map(Some),
932 None => Ok(None),
933 }
934 },
935 SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
936 panic!("procedure index unexpectedly refers to non-procedure item")
937 },
938 }
939 }
940
941 fn translate_function_type(
942 &self,
943 module_index: ModuleIndex,
944 ty: &ast::FunctionType,
945 ) -> Result<Arc<types::FunctionType>, LinkerError> {
946 use miden_assembly_syntax::ast::TypeResolver;
947
948 let cc = ty.cc.clone();
949 let mut args = Vec::with_capacity(ty.args.len());
950
951 let symbol_resolver = SymbolResolver::new(self);
952 let mut cache = ResolverCache::default();
953 let mut resolver = Resolver {
954 resolver: &symbol_resolver,
955 cache: &mut cache,
956 current_module: module_index,
957 };
958 for arg in ty.args.iter() {
959 if let Some(arg) = resolver.resolve(arg)? {
960 args.push(arg);
961 } else {
962 let span = arg.span();
963 return Err(LinkerError::UndefinedType {
964 span,
965 source_file: self.source_manager.get(span.source_id()).ok(),
966 });
967 }
968 }
969 let mut results = Vec::with_capacity(ty.results.len());
970 for result in ty.results.iter() {
971 if let Some(result) = resolver.resolve(result)? {
972 results.push(result);
973 } else {
974 let span = result.span();
975 return Err(LinkerError::UndefinedType {
976 span,
977 source_file: self.source_manager.get(span.source_id()).ok(),
978 });
979 }
980 }
981 Ok(Arc::new(types::FunctionType::new(cc, args, results)))
982 }
983
984 pub fn resolve_attributes(&self, gid: GlobalItemIndex) -> AttributeSet {
986 match self[gid].item() {
987 SymbolItem::Compiled(ItemInfo::Procedure(proc)) => proc.attributes.clone(),
988 SymbolItem::Procedure(proc) => {
989 let proc = proc.borrow();
990 proc.attributes().clone()
991 },
992 SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
993 panic!("procedure index unexpectedly refers to non-procedure item")
994 },
995 }
996 }
997
998 pub fn resolve_type(
1000 &self,
1001 span: SourceSpan,
1002 gid: GlobalItemIndex,
1003 ) -> Result<types::Type, LinkerError> {
1004 use miden_assembly_syntax::ast::{TypeResolver, constants::ConstEnvironment};
1005
1006 let symbol_resolver = SymbolResolver::new(self);
1007 let mut cache = ResolverCache::default();
1008 let mut resolver = Resolver {
1009 cache: &mut cache,
1010 resolver: &symbol_resolver,
1011 current_module: gid.module,
1012 };
1013
1014 let template = resolver.get_type(span, gid)?.ok_or_else(|| LinkerError::UndefinedType {
1015 span,
1016 source_file: resolver.get_source_file_for(span),
1017 })?;
1018 resolver.finalize(span, template)
1019 }
1020
1021 pub(crate) fn register_procedure_root(
1030 &mut self,
1031 id: GlobalItemIndex,
1032 procedure_mast_root: Word,
1033 ) {
1034 use alloc::collections::btree_map::Entry;
1035 match self.procedures_by_mast_root.entry(procedure_mast_root) {
1036 Entry::Occupied(ref mut entry) => {
1037 let prev_id = entry.get()[0];
1038 if prev_id != id {
1039 entry.get_mut().push(id);
1041 }
1042 },
1043 Entry::Vacant(entry) => {
1044 entry.insert(smallvec![id]);
1045 },
1046 }
1047 }
1048
1049 pub fn find_module_index(&self, path: &Path) -> Option<ModuleIndex> {
1051 self.modules.iter().position(|m| path == m.path()).map(ModuleIndex::new)
1052 }
1053
1054 pub fn find_module(&self, path: &Path) -> Option<&LinkModule> {
1056 self.modules.iter().find(|m| path == m.path())
1057 }
1058}
1059
1060impl Linker {
1062 pub fn const_eval(
1064 &self,
1065 gid: GlobalItemIndex,
1066 expr: &ast::ConstantExpr,
1067 cache: &mut ResolverCache,
1068 ) -> Result<ast::ConstantValue, LinkerError> {
1069 let symbol_resolver = SymbolResolver::new(self);
1070 let mut resolver = Resolver {
1071 resolver: &symbol_resolver,
1072 cache,
1073 current_module: gid.module,
1074 };
1075
1076 ast::constants::eval::expr(expr, &mut resolver).map(|expr| expr.expect_value())
1077 }
1078}
1079
1080impl Index<ModuleIndex> for Linker {
1081 type Output = LinkModule;
1082
1083 fn index(&self, index: ModuleIndex) -> &Self::Output {
1084 &self.modules[index.as_usize()]
1085 }
1086}
1087
1088impl Index<GlobalItemIndex> for Linker {
1089 type Output = Symbol;
1090
1091 fn index(&self, index: GlobalItemIndex) -> &Self::Output {
1092 &self.modules[index.module.as_usize()][index.index]
1093 }
1094}
1095
1096#[cfg(test)]
1097mod tests {
1098 use std::{
1099 collections::BTreeSet,
1100 panic::{AssertUnwindSafe, catch_unwind},
1101 string::String,
1102 sync::Arc,
1103 };
1104
1105 use miden_assembly_syntax::{
1106 ast::{
1107 Ident, InvocationTarget, InvokeKind, ItemIndex, Path, SymbolResolutionError,
1108 Visibility, types,
1109 },
1110 debuginfo::{SourceSpan, Span},
1111 module::{ItemInfo, TypeInfo},
1112 };
1113 use miden_core::Felt;
1114
1115 use super::*;
1116 use crate::{
1117 Assembler,
1118 ast::Module,
1119 testing::{TestContext, source_file},
1120 };
1121
1122 #[test]
1123 fn failed_kernel_link_restores_kernel_state() {
1124 let context = TestContext::default();
1125 let source_manager = context.source_manager();
1126 let kernel_source = r#"
1127 pub proc a
1128 call.b
1129 end
1130
1131 proc b
1132 call.a
1133 end
1134 "#;
1135
1136 let userspace = context
1137 .parse_module(source_file!(
1138 &context,
1139 r#"
1140 namespace userspace
1141
1142 pub proc helper
1143 push.1
1144 end
1145 "#
1146 ))
1147 .expect("userspace module parsing must succeed");
1148
1149 let mut linker = Linker::new(source_manager);
1150 let userspace_index = linker
1151 .link([userspace], None)
1152 .expect("userspace module must link successfully")
1153 .into_iter()
1154 .next()
1155 .expect("linked module index must be returned");
1156
1157 let first_err = linker
1158 .link_kernel(
1159 context
1160 .parse_kernel(source_file!(&context, kernel_source))
1161 .expect("kernel parsing must succeed"),
1162 None,
1163 )
1164 .expect_err("expected cyclic kernel to be rejected");
1165
1166 assert!(first_err.to_string().contains("found a cycle in the call graph"));
1167 assert!(!linker.has_nonempty_kernel(), "failed kernel link must not leave a kernel set");
1168 assert_eq!(linker[userspace_index].kind(), ast::ModuleKind::Library);
1169
1170 let second_err = linker
1171 .link_kernel(
1172 context
1173 .parse_kernel(source_file!(&context, kernel_source))
1174 .expect("kernel parsing must succeed"),
1175 None,
1176 )
1177 .expect_err("expected cyclic kernel retry to be rejected");
1178 assert!(second_err.to_string().contains("found a cycle in the call graph"));
1179 assert!(!second_err.to_string().contains("duplicate module"));
1180
1181 let syscall_context = SymbolResolutionContext {
1182 span: SourceSpan::UNKNOWN,
1183 module: userspace_index,
1184 kind: Some(InvokeKind::SysCall),
1185 };
1186 let err = linker
1187 .resolve_invoke_target(
1188 &syscall_context,
1189 &InvocationTarget::Symbol(Ident::new("a").expect("valid identifier")),
1190 )
1191 .expect_err("expected syscall without a linked kernel to be rejected");
1192 assert!(matches!(err, LinkerError::InvalidSysCallTarget { .. }));
1193 }
1194
1195 #[test]
1196 fn link_library_keeps_same_interface_libraries_with_distinct_forest_commitments() {
1197 let context = TestContext::default();
1198 let module = context
1199 .parse_module(source_file!(
1200 &context,
1201 r#"
1202 namespace lib
1203
1204 pub proc foo
1205 push.1
1206 end
1207 "#
1208 ))
1209 .expect("library module should parse");
1210 let package: Arc<MastPackage> = Assembler::new(context.source_manager())
1211 .assemble_library("lib", module, None::<Box<Module>>)
1212 .expect("library should assemble")
1213 .into();
1214 let with_advice = Arc::new(package.as_ref().clone().with_advice_map(AdviceMap::from_iter(
1215 [(Word::from([1_u32, 2, 3, 4]), vec![Felt::from_u32(5)])],
1216 )));
1217
1218 assert_ne!(package.commitment(), with_advice.commitment());
1219 assert_eq!(
1220 package.interface_commitment().unwrap(),
1221 with_advice.interface_commitment().unwrap()
1222 );
1223 assert_ne!(package.mast_forest().commitment(), with_advice.mast_forest().commitment());
1224
1225 let mut linker = Linker::new(context.source_manager());
1226 linker
1227 .link_library(LinkLibrary::from_package(package).with_linkage(Linkage::Static))
1228 .expect("first library should link");
1229 linker
1230 .link_library(LinkLibrary::from_package(with_advice).with_linkage(Linkage::Static))
1231 .expect("same public interface with distinct forest commitment should link");
1232
1233 assert_eq!(linker.libraries().count(), 1);
1234 assert_eq!(linker.static_libraries().count(), 2);
1235 }
1236
1237 #[test]
1238 fn oversized_link_module_resolution_returns_structured_error() {
1239 let context = TestContext::default();
1240 let mut linker = Linker::new(context.source_manager());
1241 let module_id = ModuleIndex::new(0);
1242 let path = Arc::<Path>::from(Path::new("::m::huge"));
1243 let mut symbols = Vec::with_capacity(ItemIndex::MAX_ITEMS + 1);
1244
1245 for i in 0..=ItemIndex::MAX_ITEMS {
1246 let name = Ident::new(format!("a{i}")).expect("valid identifier");
1247 symbols.push(Symbol::new(
1248 name.clone(),
1249 Visibility::Private,
1250 LinkStatus::Unlinked,
1251 SymbolItem::Compiled(ItemInfo::Type(TypeInfo { name, ty: types::Type::Felt })),
1252 ));
1253 }
1254
1255 linker.modules.push(
1256 LinkModule::new(
1257 module_id,
1258 ast::ModuleKind::Library,
1259 LinkStatus::Unlinked,
1260 ModuleSource::Mast,
1261 path,
1262 )
1263 .with_symbols(symbols),
1264 );
1265
1266 let result = catch_unwind(AssertUnwindSafe(|| {
1267 linker[module_id].resolve(Span::unknown("a0"), &SymbolResolver::new(&linker))
1268 }));
1269
1270 let result = match result {
1271 Ok(result) => result,
1272 Err(panic) => {
1273 let message = panic
1274 .downcast_ref::<&str>()
1275 .copied()
1276 .or_else(|| panic.downcast_ref::<String>().map(String::as_str))
1277 .expect("panic payload should be a string");
1278 panic!("expected graceful error, got panic: {message}");
1279 },
1280 };
1281
1282 assert!(matches!(
1283 result,
1284 Err(err) if matches!(*err, SymbolResolutionError::TooManyItemsInModule { .. })
1285 ));
1286 }
1287
1288 fn proc_gid(linker: &Linker, module_index: ModuleIndex, name: &str) -> GlobalItemIndex {
1290 let index = ItemIndex::new(
1291 linker[module_index]
1292 .symbols()
1293 .position(|symbol| symbol.name().as_str() == name)
1294 .expect("procedure should be present in the module"),
1295 );
1296 GlobalItemIndex { module: module_index, index }
1297 }
1298
1299 #[test]
1300 fn analysis_mode_commits_cycle_and_reports_procedure_paths() {
1301 let context = TestContext::default();
1302 let module = context
1303 .parse_module(source_file!(
1304 &context,
1305 r#"
1306 namespace proj
1307
1308 pub proc a
1309 call.b
1310 call.leaf
1311 end
1312
1313 pub proc b
1314 call.a
1315 end
1316
1317 pub proc caller
1318 call.a
1319 end
1320
1321 pub proc leaf
1322 push.1
1323 end
1324
1325 pub proc independent
1326 push.1
1327 end
1328 "#
1329 ))
1330 .expect("cyclic module must parse");
1331
1332 let mut linker = Linker::new(context.source_manager());
1333 let analysis = linker
1334 .link_analysis([module], None)
1335 .expect("analysis link must not reject a static cycle");
1336
1337 assert!(
1339 !analysis.module_indices.is_empty(),
1340 "analysis must return linked module indices"
1341 );
1342 assert!(analysis.has_cycle(), "analysis must report the static recursion cycle");
1343 let cycle: BTreeSet<String> = analysis.cycle.iter().cloned().collect();
1344 assert_eq!(
1345 cycle,
1346 BTreeSet::from(["::proj::a".to_string(), "::proj::b".to_string()]),
1347 "cycle diagnostic must exclude acyclic callees"
1348 );
1349
1350 let module_index = analysis.module_indices[0];
1351
1352 let a = proc_gid(&linker, module_index, "a");
1354 let caller = proc_gid(&linker, module_index, "caller");
1355 assert!(
1356 linker.topological_sort_from_root(a).is_err(),
1357 "a procedure in the cycle must be detected as part of a cycle"
1358 );
1359 assert!(
1360 linker.topological_sort_from_root(caller).is_err(),
1361 "a caller that depends on the cycle must be detected alongside it"
1362 );
1363
1364 let independent = proc_gid(&linker, module_index, "independent");
1366 let sorted = linker
1367 .topological_sort_from_root(independent)
1368 .expect("a procedure outside the cycle must still be liftable");
1369 assert_eq!(sorted, vec![independent]);
1370
1371 let repeated = linker
1373 .link_analysis([], [])
1374 .expect("repeated analysis must preserve the cycle diagnostic");
1375 assert_eq!(repeated.cycle, analysis.cycle);
1376
1377 let err = linker
1378 .link([], [])
1379 .expect_err("strict linking must reject a cycle committed by analysis mode");
1380 assert!(
1381 err.to_string().contains("found a cycle in the call graph"),
1382 "strict link should report the committed cycle, got: {err}"
1383 );
1384 }
1385
1386 #[test]
1387 fn analysis_mode_reports_no_cycle_for_acyclic_graph() {
1388 let context = TestContext::default();
1389 let module = context
1390 .parse_module(source_file!(
1391 &context,
1392 r#"
1393 namespace proj
1394
1395 pub proc a
1396 push.1
1397 end
1398
1399 pub proc b
1400 call.a
1401 end
1402 "#
1403 ))
1404 .expect("acyclic module must parse");
1405
1406 let mut linker = Linker::new(context.source_manager());
1407 let analysis = linker
1408 .link_analysis([module], None)
1409 .expect("analysis link must succeed for an acyclic graph");
1410
1411 assert!(!analysis.has_cycle(), "an acyclic graph must not report a cycle");
1412 assert!(analysis.cycle.is_empty());
1413 assert!(!analysis.module_indices.is_empty());
1414
1415 let module_index = analysis.module_indices[0];
1416 let a = proc_gid(&linker, module_index, "a");
1417 let b = proc_gid(&linker, module_index, "b");
1418 let sorted = linker
1419 .topological_sort_from_root(b)
1420 .expect("an acyclic graph must be fully liftable");
1421 assert_eq!(sorted, vec![b, a]);
1422 }
1423
1424 #[test]
1425 fn strict_mode_rejects_cycle_and_rolls_back() {
1426 let context = TestContext::default();
1427 let module = context
1428 .parse_module(source_file!(
1429 &context,
1430 r#"
1431 namespace proj
1432
1433 pub proc a
1434 call.b
1435 end
1436
1437 pub proc b
1438 call.a
1439 end
1440 "#
1441 ))
1442 .expect("cyclic module must parse");
1443
1444 let mut linker = Linker::new(context.source_manager());
1445 let err = linker
1446 .link([module], None)
1447 .expect_err("strict linking must reject a static cycle before MAST is built");
1448 assert!(
1449 err.to_string().contains("found a cycle in the call graph"),
1450 "strict link should report the cycle, got: {err}"
1451 );
1452
1453 let module_index = linker
1456 .modules()
1457 .iter()
1458 .position(|module| module.path().as_str().ends_with("::proj"))
1459 .map(ModuleIndex::new)
1460 .expect("module should still be present after a rolled-back link");
1461 assert!(
1462 linker[module_index].is_unlinked(),
1463 "a failed strict link must not mark modules as linked"
1464 );
1465 let a = proc_gid(&linker, module_index, "a");
1466 assert_eq!(
1467 linker
1468 .topological_sort_from_root(a)
1469 .expect("a failed strict link must not commit any call edges"),
1470 vec![a],
1471 );
1472 }
1473
1474 #[test]
1475 fn fatal_link_error_rolls_back_symbol_status() {
1476 for mode in [LinkMode::Strict, LinkMode::Analysis] {
1477 let context = TestContext::default();
1478 let module = context
1479 .parse_module(source_file!(
1480 &context,
1481 r#"
1482 namespace proj
1483
1484 pub proc linked
1485 push.1
1486 end
1487
1488 pub proc unresolved
1489 call.::support::missing
1490 end
1491 "#
1492 ))
1493 .expect("module with an unresolved call must parse");
1494 let support = context
1495 .parse_module(source_file!(
1496 &context,
1497 r#"
1498 namespace support
1499
1500 pub proc present
1501 push.1
1502 end
1503 "#
1504 ))
1505 .expect("support module must parse");
1506
1507 let mut linker = Linker::new(context.source_manager());
1508 let err = match mode {
1509 LinkMode::Strict => linker.link([module], [support]).map(drop),
1510 LinkMode::Analysis => linker.link_analysis([module], [support]).map(drop),
1511 }
1512 .expect_err("an unresolved call must be fatal");
1513
1514 assert!(
1515 !err.to_string().contains("found a cycle in the call graph"),
1516 "an unresolved call must not be reported as a cycle, got: {err}"
1517 );
1518 assert!(linker[ModuleIndex::new(0)].is_unlinked());
1519 assert!(linker[ModuleIndex::new(0)].symbols().all(Symbol::is_unlinked));
1520 }
1521 }
1522}