1mod callgraph;
39mod debug;
40mod errors;
41mod library;
42mod module;
43pub mod namespaces;
44mod resolver;
45mod rewrites;
46mod symbols;
47
48use alloc::{boxed::Box, collections::BTreeMap, string::ToString, sync::Arc, vec::Vec};
49use core::{
50 cell::RefCell,
51 ops::{ControlFlow, Index},
52};
53
54use miden_assembly_syntax::{
55 Report,
56 ast::{
57 self, AttributeSet, GlobalItemIndex, InvocationTarget, ItemIndex, Module, ModuleIndex,
58 Path, SymbolResolution, Visibility, types,
59 },
60 debuginfo::{SourceManager, SourceSpan, Span, Spanned},
61 module::{ItemInfo, ModuleDescriptor},
62};
63use miden_core::{Word, advice::AdviceMap, program::KernelDescriptor};
64use miden_mast_package::Package as MastPackage;
65use smallvec::{SmallVec, smallvec};
66
67pub use self::{
68 callgraph::{CallGraph, CycleError},
69 errors::LinkerError,
70 library::{LinkLibrary, Linkage},
71 namespaces::NamespaceGraph,
72 resolver::{ResolverCache, SymbolResolutionContext, SymbolResolver},
73 symbols::{Import, Symbol, SymbolItem},
74};
75use self::{
76 module::{LinkModule, ModuleSource},
77 namespaces::ResolvedImports,
78 resolver::*,
79};
80
81#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
83pub enum LinkStatus {
84 #[default]
86 Unlinked,
87 PartiallyLinked,
90 Linked,
92}
93
94#[derive(Clone)]
127pub struct Linker {
128 libraries: BTreeMap<Word, LinkLibrary>,
130 static_libraries: BTreeMap<Word, LinkLibrary>,
135 modules: Vec<LinkModule>,
137 callgraph: CallGraph,
140 procedures_by_mast_root: BTreeMap<Word, SmallVec<[GlobalItemIndex; 1]>>,
143 kernel_index: Option<ModuleIndex>,
145 kernel: KernelDescriptor,
149 kernel_package: Option<Arc<MastPackage>>,
150 source_manager: Arc<dyn SourceManager>,
152}
153
154impl Linker {
157 pub fn new(source_manager: Arc<dyn SourceManager>) -> Self {
159 Self {
160 libraries: Default::default(),
161 static_libraries: Default::default(),
162 modules: Default::default(),
163 callgraph: Default::default(),
164 procedures_by_mast_root: Default::default(),
165 kernel_index: None,
166 kernel: Default::default(),
167 kernel_package: None,
168 source_manager,
169 }
170 }
171
172 pub fn link_library(&mut self, library: LinkLibrary) -> Result<(), LinkerError> {
174 use alloc::collections::btree_map::Entry;
175
176 let module_descriptors = library.module_descriptors().map_err(|err| {
177 LinkerError::InvalidPackageModuleSurface {
178 package: library.package.name.to_string(),
179 reason: err.to_string(),
180 }
181 })?;
182 let library_interface_digest =
183 library
184 .interface_digest()
185 .map_err(|err| LinkerError::InvalidPackageModuleSurface {
186 package: library.package.name.to_string(),
187 reason: err.to_string(),
188 })?;
189
190 let static_library = matches!(library.linkage, Linkage::Static).then(|| library.clone());
191 let result = match self.libraries.entry(library_interface_digest) {
192 Entry::Vacant(entry) => {
193 entry.insert(library);
194 self.link_assembled_modules(module_descriptors)
195 },
196 Entry::Occupied(mut entry) => {
197 let prev = entry.get_mut();
198
199 if matches!(prev.linkage, Linkage::Dynamic) {
202 prev.linkage = library.linkage;
203 }
204
205 Ok(())
206 },
207 };
208
209 if result.is_ok()
210 && let Some(static_library) = static_library
211 {
212 self.static_libraries
213 .entry(static_library.commitment())
214 .or_insert(static_library);
215 }
216
217 result
218 }
219
220 pub fn link_assembled_modules(
225 &mut self,
226 modules: impl IntoIterator<Item = ModuleDescriptor>,
227 ) -> Result<(), LinkerError> {
228 for module in modules {
229 self.link_assembled_module(module)?;
230 }
231
232 Ok(())
233 }
234
235 pub fn link_assembled_module(
240 &mut self,
241 module: ModuleDescriptor,
242 ) -> Result<ModuleIndex, LinkerError> {
243 log::debug!(target: "linker", "adding pre-assembled module {} to module graph", module.path());
244
245 let module_path = module.path();
246 let is_duplicate = self.find_module_index(module_path).is_some();
247 if is_duplicate {
248 return Err(LinkerError::DuplicateModule {
249 path: module_path.to_path_buf().into_boxed_path().into(),
250 });
251 }
252
253 let module_index = self.next_module_id();
254 let submodules = module.submodules().to_vec();
255 let items = module.items();
256 let mut symbols = Vec::with_capacity(items.len());
257 for (idx, item) in items {
258 let gid = module_index + idx;
259 self.callgraph.get_or_insert_node(gid);
260 match &item {
261 ItemInfo::Procedure(item) => {
262 self.register_procedure_root(gid, item.digest);
263 },
264 ItemInfo::Constant(_) | ItemInfo::Type(_) => (),
265 }
266 symbols.push(Symbol::new(
267 item.name().clone(),
268 Visibility::Public,
269 LinkStatus::Linked,
270 SymbolItem::Compiled(item.clone()),
271 ));
272 }
273
274 let link_module = LinkModule::new(
275 module_index,
276 ast::ModuleKind::Library,
277 LinkStatus::Linked,
278 ModuleSource::Mast,
279 module_path.into(),
280 )
281 .with_submodules(submodules)
282 .with_symbols(symbols);
283
284 self.modules.push(link_module);
285 Ok(module_index)
286 }
287
288 pub fn link_modules(
292 &mut self,
293 modules: impl IntoIterator<Item = Box<Module>>,
294 ) -> Result<Vec<ModuleIndex>, LinkerError> {
295 modules.into_iter().map(|mut m| self.link_module(&mut m)).collect()
296 }
297
298 pub fn link_module(&mut self, module: &mut Module) -> Result<ModuleIndex, LinkerError> {
317 log::debug!(target: "linker", "adding unprocessed module {}", module.path());
318
319 let is_duplicate = self.find_module_index(module.path()).is_some();
320 if is_duplicate {
321 return Err(LinkerError::DuplicateModule { path: module.path().into() });
322 }
323
324 let module_index = self.next_module_id();
325 let submodules = module.submodules().to_vec();
326 let mut symbols = Vec::new();
327 let imports = module.take_imports().into_iter().map(Import::new).collect::<Vec<_>>();
328 for item in module.take_items() {
329 match item {
330 ast::Item::Type(item) => {
331 let gid = module_index + ItemIndex::new(symbols.len());
332 self.callgraph.get_or_insert_node(gid);
333 symbols.push(Symbol::new(
334 item.name().clone(),
335 item.visibility(),
336 LinkStatus::Unlinked,
337 SymbolItem::Type(item),
338 ));
339 },
340 ast::Item::Constant(item) => {
341 let gid = module_index + ItemIndex::new(symbols.len());
342 self.callgraph.get_or_insert_node(gid);
343 symbols.push(Symbol::new(
344 item.name().clone(),
345 item.visibility,
346 LinkStatus::Unlinked,
347 SymbolItem::Constant(item),
348 ));
349 },
350 ast::Item::Procedure(item) => {
351 let gid = module_index + ItemIndex::new(symbols.len());
352 self.callgraph.get_or_insert_node(gid);
353 symbols.push(Symbol::new(
354 item.name().clone().into(),
355 item.visibility(),
356 LinkStatus::Unlinked,
357 SymbolItem::Procedure(RefCell::new(Box::new(item))),
358 ));
359 },
360 }
361 }
362 let link_module = LinkModule::new(
363 module_index,
364 module.kind(),
365 LinkStatus::Unlinked,
366 ModuleSource::Ast,
367 module.path().into(),
368 )
369 .with_advice_map(module.advice_map().clone())
370 .with_submodules(submodules)
371 .with_imports(imports)
372 .with_symbols(symbols);
373
374 self.modules.push(link_module);
375 Ok(module_index)
376 }
377
378 #[inline]
379 fn next_module_id(&self) -> ModuleIndex {
380 ModuleIndex::new(self.modules.len())
381 }
382}
383
384impl Linker {
387 pub fn with_kernel(
391 source_manager: Arc<dyn SourceManager>,
392 kernel_package: Arc<MastPackage>,
393 ) -> Result<Self, Report> {
394 log::debug!(target: "linker", "instantiating linker with kernel package {}@{}", kernel_package.name, kernel_package.version);
395
396 let mut linker = Self::new(source_manager);
397 linker.link_with_kernel(kernel_package)?;
398
399 Ok(linker)
400 }
401
402 pub fn link_with_kernel(&mut self, kernel_package: Arc<MastPackage>) -> Result<(), Report> {
410 if !kernel_package.is_kernel() {
411 return Err(Report::msg("invalid kernel package: not a kernel"));
412 }
413 let kernel = kernel_package.to_kernel_descriptor()?;
414 if kernel.is_empty() {
415 return Err(Report::msg("invalid kernel package: kernel cannot be empty"));
416 }
417 assert!(self.kernel.is_empty());
418 assert!(self.kernel_package.is_none());
419
420 log::debug!(target: "linker", "modifying linker with kernel package {}@{}", kernel_package.name, kernel_package.version);
421
422 let mut kernel_index = None;
423 let module_descriptors = kernel_package.try_module_descriptors().map_err(|err| {
424 LinkerError::InvalidPackageModuleSurface {
425 package: kernel_package.name.to_string(),
426 reason: err.to_string(),
427 }
428 })?;
429 for module_descriptor in module_descriptors {
430 let is_kernel_module = module_descriptor.path().is_kernel_path();
431 let module_index = self.link_assembled_module(module_descriptor)?;
432 if is_kernel_module {
433 kernel_index = Some(module_index);
434 }
435 }
436 assert!(kernel_index.is_some());
437
438 self.kernel_index = kernel_index;
439 self.kernel = kernel;
440 self.kernel_package = Some(kernel_package);
441
442 Ok(())
443 }
444
445 pub fn kernel(&self) -> &KernelDescriptor {
446 &self.kernel
447 }
448
449 pub fn kernel_package(&self) -> Option<Arc<MastPackage>> {
450 self.kernel_package.clone()
451 }
452
453 pub fn has_nonempty_kernel(&self) -> bool {
454 self.kernel_index.is_some() || !self.kernel.is_empty()
455 }
456}
457
458impl Linker {
461 fn cycle_error(&self, cycle: CycleError) -> LinkerError {
462 let iter = cycle.into_node_ids();
463 let mut nodes = Vec::with_capacity(iter.len());
464 for node in iter {
465 let module = self[node.module].path();
466 let item = self[node].name();
467 nodes.push(module.join(item).to_string());
468 }
469 LinkerError::Cycle { nodes: nodes.into() }
470 }
471
472 pub fn link(
479 &mut self,
480 roots: impl IntoIterator<Item = Box<Module>>,
481 support: impl IntoIterator<Item = Box<Module>>,
482 ) -> Result<Vec<ModuleIndex>, LinkerError> {
483 use alloc::collections::BTreeSet;
484
485 let root_indices = self.link_modules(roots)?;
486 let _support_indices = self.link_modules(support)?;
487 let namespaces = NamespaceGraph::build(self)?;
488 let imports = namespaces.resolve_imports(self)?;
489
490 self.link_and_rewrite(&namespaces, &imports)?;
491
492 let mut reachable = BTreeSet::new();
493
494 for root in root_indices {
495 reachable.extend(namespaces.reachable_from_root(root));
496 }
497
498 Ok(reachable.into_iter().collect())
499 }
500
501 pub fn link_kernel(
509 &mut self,
510 mut kernel: Box<Module>,
511 support: impl IntoIterator<Item = Box<Module>>,
512 ) -> Result<Vec<ModuleIndex>, LinkerError> {
513 self.link_modules(support)?;
514 let original_module_len = self.modules.len();
515 let original_callgraph = self.callgraph.clone();
516 let module_index = self.link_module(&mut kernel)?;
517 let original_kernel_index = self.kernel_index;
518 let original_module_kinds = self
519 .modules
520 .iter()
521 .enumerate()
522 .take(module_index.as_usize())
523 .filter(|(_, module)| matches!(module.source(), ModuleSource::Ast))
524 .map(|(module_index, module)| (module_index, module.kind()))
525 .collect::<Vec<_>>();
526
527 for module in self.modules.iter_mut().take(module_index.as_usize()) {
529 if matches!(module.source(), ModuleSource::Ast) {
530 module.set_kind(ast::ModuleKind::Kernel);
531 }
532 }
533
534 self.kernel_index = Some(module_index);
535
536 let result = (|| {
537 let namespaces = NamespaceGraph::build(self)?;
538 let imports = namespaces.resolve_imports(self)?;
539 self.link_and_rewrite(&namespaces, &imports)?;
540
541 Ok(namespaces.reachable_from_root(module_index))
542 })();
543
544 match result {
545 ok @ Ok(_) => ok,
546 err => {
547 self.kernel_index = original_kernel_index;
548 self.callgraph = original_callgraph;
549 self.modules.truncate(original_module_len);
550 for (module_index, module_kind) in original_module_kinds {
551 self.modules[module_index].set_kind(module_kind);
552 }
553
554 err
555 },
556 }
557 }
558
559 fn link_and_rewrite(
598 &mut self,
599 namespaces: &NamespaceGraph,
600 imports: &ResolvedImports,
601 ) -> Result<(), LinkerError> {
602 log::debug!(
603 target: "linker",
604 "processing {} unlinked/partially-linked modules, and recomputing module graph",
605 self.modules.iter().filter(|m| !m.is_linked()).count()
606 );
607
608 if self.modules.is_empty() {
611 return Err(LinkerError::Empty);
612 }
613
614 if self.modules.iter().all(LinkModule::is_linked) {
616 return Ok(());
617 }
618
619 let pending_modules = self
622 .modules
623 .iter()
624 .enumerate()
625 .filter(|(_, module)| module.is_unlinked())
626 .map(|(module_index, module)| (module_index, module.clone()))
627 .collect::<Vec<_>>();
628 let original_callgraph = self.callgraph.clone();
629
630 let result = {
631 let resolver = SymbolResolver::with_namespaces(self, namespaces, imports);
632 let mut edges = Vec::new();
633 let mut cache = ResolverCache::default();
634 let mut linked_modules = Vec::new();
635
636 for (module_index, module) in self.modules.iter().enumerate() {
637 if !module.is_unlinked() {
638 continue;
639 }
640
641 let module_index = ModuleIndex::new(module_index);
642
643 for import in module.imports() {
644 if let Some(namespaces::ResolvedUse::Item(gid)) =
645 imports.get(module_index, import.local_name().as_str())
646 {
647 import.set_resolved(gid);
648 }
649 }
650
651 for (symbol_idx, symbol) in module.symbols().enumerate() {
652 let gid = module_index + ItemIndex::new(symbol_idx);
653
654 rewrites::rewrite_symbol(gid, symbol, &resolver, &mut cache)?;
656
657 match symbol.item() {
659 SymbolItem::Compiled(_) | SymbolItem::Type(_) | SymbolItem::Constant(_) => {
660 },
661 SymbolItem::Procedure(proc) => {
662 let proc = proc.borrow();
665 for invoke in proc.invoked() {
666 log::debug!(target: "linker", " | recording {} dependency on {}", invoke.kind, invoke.target);
667
668 let context = SymbolResolutionContext {
669 span: invoke.span(),
670 module: module_index,
671 kind: Some(invoke.kind),
672 };
673 if let Some(callee) = resolver
674 .resolve_invoke_target(&context, &invoke.target)?
675 .into_global_id()
676 {
677 log::debug!(
678 target: "linker",
679 " | resolved dependency to gid {}:{}",
680 callee.module.as_usize(),
681 callee.index.as_usize()
682 );
683 edges.push((gid, callee));
684 }
685 }
686 },
687 }
688 }
689
690 linked_modules.push(module_index);
691 }
692
693 let mut callgraph = self.callgraph.clone();
694 for (caller, callee) in edges {
695 callgraph.add_edge(caller, callee).map_err(|cycle| self.cycle_error(cycle))?;
696 }
697
698 callgraph.toposort().map_err(|cycle| self.cycle_error(cycle))?;
700
701 Ok::<_, LinkerError>((linked_modules, callgraph))
702 };
703
704 match result {
705 Ok((linked_modules, callgraph)) => {
706 self.callgraph = callgraph;
707 for module_index in linked_modules {
708 self.modules[module_index.as_usize()].set_status(LinkStatus::Linked);
709 }
710 },
711 Err(err) => {
712 self.callgraph = original_callgraph;
713 for (module_index, module) in pending_modules {
714 self.modules[module_index] = module;
715 }
716 return Err(err);
717 },
718 }
719
720 Ok(())
721 }
722}
723
724impl Linker {
727 pub fn modules(&self) -> &[LinkModule] {
729 self.modules.as_slice()
730 }
731
732 pub fn libraries(&self) -> impl Iterator<Item = &LinkLibrary> {
734 self.libraries.values()
735 }
736
737 pub fn static_libraries(&self) -> impl Iterator<Item = &LinkLibrary> {
739 self.static_libraries.values()
740 }
741
742 pub fn topological_sort_from_root(
744 &self,
745 caller: GlobalItemIndex,
746 ) -> Result<Vec<GlobalItemIndex>, CycleError> {
747 self.callgraph.toposort_caller(caller)
748 }
749
750 pub fn get_procedure_index_by_digest(
755 &self,
756 procedure_digest: &Word,
757 ) -> Option<GlobalItemIndex> {
758 self.procedures_by_mast_root.get(procedure_digest).map(|indices| indices[0])
759 }
760
761 pub fn resolve_invoke_target(
763 &self,
764 caller: &SymbolResolutionContext,
765 target: &InvocationTarget,
766 ) -> Result<SymbolResolution, LinkerError> {
767 let namespaces = NamespaceGraph::build(self)?;
768 let imports = namespaces.resolve_imports(self)?;
769 let resolver = SymbolResolver::with_namespaces(self, &namespaces, &imports);
770 resolver.resolve_invoke_target(caller, target)
771 }
772
773 pub fn resolve_path(
775 &self,
776 caller: &SymbolResolutionContext,
777 path: &Path,
778 ) -> Result<SymbolResolution, LinkerError> {
779 let namespaces = NamespaceGraph::build(self)?;
780 let imports = namespaces.resolve_imports(self)?;
781 let resolver = SymbolResolver::with_namespaces(self, &namespaces, &imports);
782 resolver.resolve_path(caller, Span::new(caller.span, path))
783 }
784
785 pub fn resolve_signature(
787 &self,
788 gid: GlobalItemIndex,
789 ) -> Result<Option<Arc<types::FunctionType>>, LinkerError> {
790 match self[gid].item() {
791 SymbolItem::Compiled(ItemInfo::Procedure(proc)) => Ok(proc.signature.clone()),
792 SymbolItem::Procedure(proc) => {
793 let proc = proc.borrow();
794 match proc.signature() {
795 Some(ty) => self.translate_function_type(gid.module, ty).map(Some),
796 None => Ok(None),
797 }
798 },
799 SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
800 panic!("procedure index unexpectedly refers to non-procedure item")
801 },
802 }
803 }
804
805 fn translate_function_type(
806 &self,
807 module_index: ModuleIndex,
808 ty: &ast::FunctionType,
809 ) -> Result<Arc<types::FunctionType>, LinkerError> {
810 use miden_assembly_syntax::ast::TypeResolver;
811
812 let cc = ty.cc;
813 let mut args = Vec::with_capacity(ty.args.len());
814
815 let symbol_resolver = SymbolResolver::new(self);
816 let mut cache = ResolverCache::default();
817 let mut resolver = Resolver {
818 resolver: &symbol_resolver,
819 cache: &mut cache,
820 current_module: module_index,
821 };
822 for arg in ty.args.iter() {
823 if let Some(arg) = resolver.resolve(arg)? {
824 args.push(arg);
825 } else {
826 let span = arg.span();
827 return Err(LinkerError::UndefinedType {
828 span,
829 source_file: self.source_manager.get(span.source_id()).ok(),
830 });
831 }
832 }
833 let mut results = Vec::with_capacity(ty.results.len());
834 for result in ty.results.iter() {
835 if let Some(result) = resolver.resolve(result)? {
836 results.push(result);
837 } else {
838 let span = result.span();
839 return Err(LinkerError::UndefinedType {
840 span,
841 source_file: self.source_manager.get(span.source_id()).ok(),
842 });
843 }
844 }
845 Ok(Arc::new(types::FunctionType::new(cc, args, results)))
846 }
847
848 pub fn resolve_attributes(&self, gid: GlobalItemIndex) -> AttributeSet {
850 match self[gid].item() {
851 SymbolItem::Compiled(ItemInfo::Procedure(proc)) => proc.attributes.clone(),
852 SymbolItem::Procedure(proc) => {
853 let proc = proc.borrow();
854 proc.attributes().clone()
855 },
856 SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
857 panic!("procedure index unexpectedly refers to non-procedure item")
858 },
859 }
860 }
861
862 pub fn resolve_type(
864 &self,
865 span: SourceSpan,
866 gid: GlobalItemIndex,
867 ) -> Result<types::Type, LinkerError> {
868 use miden_assembly_syntax::ast::TypeResolver;
869
870 let symbol_resolver = SymbolResolver::new(self);
871 let mut cache = ResolverCache::default();
872 let mut resolver = Resolver {
873 cache: &mut cache,
874 resolver: &symbol_resolver,
875 current_module: gid.module,
876 };
877
878 resolver.get_type(span, gid)
879 }
880
881 pub(crate) fn register_procedure_root(
890 &mut self,
891 id: GlobalItemIndex,
892 procedure_mast_root: Word,
893 ) {
894 use alloc::collections::btree_map::Entry;
895 match self.procedures_by_mast_root.entry(procedure_mast_root) {
896 Entry::Occupied(ref mut entry) => {
897 let prev_id = entry.get()[0];
898 if prev_id != id {
899 entry.get_mut().push(id);
901 }
902 },
903 Entry::Vacant(entry) => {
904 entry.insert(smallvec![id]);
905 },
906 }
907 }
908
909 pub fn find_module_index(&self, path: &Path) -> Option<ModuleIndex> {
911 self.modules.iter().position(|m| path == m.path()).map(ModuleIndex::new)
912 }
913
914 pub fn find_module(&self, path: &Path) -> Option<&LinkModule> {
916 self.modules.iter().find(|m| path == m.path())
917 }
918}
919
920impl Linker {
922 pub fn const_eval(
924 &self,
925 gid: GlobalItemIndex,
926 expr: &ast::ConstantExpr,
927 cache: &mut ResolverCache,
928 ) -> Result<ast::ConstantValue, LinkerError> {
929 let symbol_resolver = SymbolResolver::new(self);
930 let mut resolver = Resolver {
931 resolver: &symbol_resolver,
932 cache,
933 current_module: gid.module,
934 };
935
936 ast::constants::eval::expr(expr, &mut resolver).map(|expr| expr.expect_value())
937 }
938}
939
940impl Index<ModuleIndex> for Linker {
941 type Output = LinkModule;
942
943 fn index(&self, index: ModuleIndex) -> &Self::Output {
944 &self.modules[index.as_usize()]
945 }
946}
947
948impl Index<GlobalItemIndex> for Linker {
949 type Output = Symbol;
950
951 fn index(&self, index: GlobalItemIndex) -> &Self::Output {
952 &self.modules[index.module.as_usize()][index.index]
953 }
954}
955
956#[cfg(test)]
957mod tests {
958 use std::{
959 panic::{AssertUnwindSafe, catch_unwind},
960 string::String,
961 sync::Arc,
962 };
963
964 use miden_assembly_syntax::{
965 ast::{
966 Ident, InvocationTarget, InvokeKind, ItemIndex, Path, SymbolResolutionError,
967 Visibility, types,
968 },
969 debuginfo::{SourceSpan, Span},
970 module::{ItemInfo, TypeInfo},
971 };
972 use miden_core::Felt;
973
974 use super::*;
975 use crate::{
976 Assembler,
977 ast::Module,
978 testing::{TestContext, source_file},
979 };
980
981 #[test]
982 fn failed_kernel_link_restores_kernel_state() {
983 let context = TestContext::default();
984 let source_manager = context.source_manager();
985 let kernel_source = r#"
986 pub proc a
987 call.b
988 end
989
990 proc b
991 call.a
992 end
993 "#;
994
995 let userspace = context
996 .parse_module(source_file!(
997 &context,
998 r#"
999 namespace userspace
1000
1001 pub proc helper
1002 push.1
1003 end
1004 "#
1005 ))
1006 .expect("userspace module parsing must succeed");
1007
1008 let mut linker = Linker::new(source_manager);
1009 let userspace_index = linker
1010 .link([userspace], None)
1011 .expect("userspace module must link successfully")
1012 .into_iter()
1013 .next()
1014 .expect("linked module index must be returned");
1015
1016 let first_err = linker
1017 .link_kernel(
1018 context
1019 .parse_kernel(source_file!(&context, kernel_source))
1020 .expect("kernel parsing must succeed"),
1021 None,
1022 )
1023 .expect_err("expected cyclic kernel to be rejected");
1024
1025 assert!(first_err.to_string().contains("found a cycle in the call graph"));
1026 assert!(!linker.has_nonempty_kernel(), "failed kernel link must not leave a kernel set");
1027 assert_eq!(linker[userspace_index].kind(), ast::ModuleKind::Library);
1028
1029 let second_err = linker
1030 .link_kernel(
1031 context
1032 .parse_kernel(source_file!(&context, kernel_source))
1033 .expect("kernel parsing must succeed"),
1034 None,
1035 )
1036 .expect_err("expected cyclic kernel retry to be rejected");
1037 assert!(second_err.to_string().contains("found a cycle in the call graph"));
1038 assert!(!second_err.to_string().contains("duplicate module"));
1039
1040 let syscall_context = SymbolResolutionContext {
1041 span: SourceSpan::UNKNOWN,
1042 module: userspace_index,
1043 kind: Some(InvokeKind::SysCall),
1044 };
1045 let err = linker
1046 .resolve_invoke_target(
1047 &syscall_context,
1048 &InvocationTarget::Symbol(Ident::new("a").expect("valid identifier")),
1049 )
1050 .expect_err("expected syscall without a linked kernel to be rejected");
1051 assert!(matches!(err, LinkerError::InvalidSysCallTarget { .. }));
1052 }
1053
1054 #[test]
1055 fn link_library_keeps_same_interface_libraries_with_distinct_forest_commitments() {
1056 let context = TestContext::default();
1057 let module = context
1058 .parse_module(source_file!(
1059 &context,
1060 r#"
1061 namespace lib
1062
1063 pub proc foo
1064 push.1
1065 end
1066 "#
1067 ))
1068 .expect("library module should parse");
1069 let package: Arc<MastPackage> = Assembler::new(context.source_manager())
1070 .assemble_library("lib", module, None::<Box<Module>>)
1071 .expect("library should assemble")
1072 .into();
1073 let with_advice = Arc::new(package.as_ref().clone().with_advice_map(AdviceMap::from_iter(
1074 [(Word::from([1_u32, 2, 3, 4]), vec![Felt::from_u32(5)])],
1075 )));
1076
1077 assert_ne!(package.digest(), with_advice.digest());
1078 assert_eq!(package.interface_digest().unwrap(), with_advice.interface_digest().unwrap());
1079 assert_ne!(package.mast_forest().commitment(), with_advice.mast_forest().commitment());
1080
1081 let mut linker = Linker::new(context.source_manager());
1082 linker
1083 .link_library(LinkLibrary::from_package(package).with_linkage(Linkage::Static))
1084 .expect("first library should link");
1085 linker
1086 .link_library(LinkLibrary::from_package(with_advice).with_linkage(Linkage::Static))
1087 .expect("same public interface with distinct forest commitment should link");
1088
1089 assert_eq!(linker.libraries().count(), 1);
1090 assert_eq!(linker.static_libraries().count(), 2);
1091 }
1092
1093 #[test]
1094 fn oversized_link_module_resolution_returns_structured_error() {
1095 let context = TestContext::default();
1096 let mut linker = Linker::new(context.source_manager());
1097 let module_id = ModuleIndex::new(0);
1098 let path = Arc::<Path>::from(Path::new("::m::huge"));
1099 let mut symbols = Vec::with_capacity(ItemIndex::MAX_ITEMS + 1);
1100
1101 for i in 0..=ItemIndex::MAX_ITEMS {
1102 let name = Ident::new(format!("a{i}")).expect("valid identifier");
1103 symbols.push(Symbol::new(
1104 name.clone(),
1105 Visibility::Private,
1106 LinkStatus::Unlinked,
1107 SymbolItem::Compiled(ItemInfo::Type(TypeInfo { name, ty: types::Type::Felt })),
1108 ));
1109 }
1110
1111 linker.modules.push(
1112 LinkModule::new(
1113 module_id,
1114 ast::ModuleKind::Library,
1115 LinkStatus::Unlinked,
1116 ModuleSource::Mast,
1117 path,
1118 )
1119 .with_symbols(symbols),
1120 );
1121
1122 let result = catch_unwind(AssertUnwindSafe(|| {
1123 linker[module_id].resolve(Span::unknown("a0"), &SymbolResolver::new(&linker))
1124 }));
1125
1126 let result = match result {
1127 Ok(result) => result,
1128 Err(panic) => {
1129 let message = panic
1130 .downcast_ref::<&str>()
1131 .copied()
1132 .or_else(|| panic.downcast_ref::<String>().map(String::as_str))
1133 .expect("panic payload should be a string");
1134 panic!("expected graceful error, got panic: {message}");
1135 },
1136 };
1137
1138 assert!(matches!(
1139 result,
1140 Err(err) if matches!(*err, SymbolResolutionError::TooManyItemsInModule { .. })
1141 ));
1142 }
1143}