1mod callgraph;
39mod debug;
40mod errors;
41mod library;
42mod module;
43mod 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, ModuleInfo},
62};
63use miden_core::{Word, advice::AdviceMap, mast::MastNodeId, program::Kernel};
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 resolver::{ResolverCache, SymbolResolutionContext, SymbolResolver},
72 symbols::{Import, Symbol, SymbolItem},
73};
74use self::{
75 module::{LinkModule, ModuleSource},
76 namespaces::{NamespaceGraph, ResolvedImports},
77 resolver::*,
78};
79
80#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
82pub enum LinkStatus {
83 #[default]
85 Unlinked,
86 PartiallyLinked,
89 Linked,
91}
92
93#[derive(Clone)]
126pub struct Linker {
127 libraries: BTreeMap<Word, LinkLibrary>,
129 static_libraries: BTreeMap<Word, LinkLibrary>,
134 modules: Vec<LinkModule>,
136 callgraph: CallGraph,
139 procedures_by_mast_root: BTreeMap<Word, SmallVec<[GlobalItemIndex; 1]>>,
142 kernel_index: Option<ModuleIndex>,
144 kernel: Kernel,
148 kernel_package: Option<Arc<MastPackage>>,
149 source_manager: Arc<dyn SourceManager>,
151}
152
153impl Linker {
156 pub fn new(source_manager: Arc<dyn SourceManager>) -> Self {
158 Self {
159 libraries: Default::default(),
160 static_libraries: Default::default(),
161 modules: Default::default(),
162 callgraph: Default::default(),
163 procedures_by_mast_root: Default::default(),
164 kernel_index: None,
165 kernel: Default::default(),
166 kernel_package: None,
167 source_manager,
168 }
169 }
170
171 pub fn link_library(&mut self, library: LinkLibrary) -> Result<(), LinkerError> {
173 use alloc::collections::btree_map::Entry;
174
175 let module_infos =
176 library.module_infos().map_err(|err| LinkerError::InvalidPackageModuleSurface {
177 package: library.package.name.to_string(),
178 reason: err.to_string(),
179 })?;
180 let library_interface_digest = library.package.interface_digest().map_err(|err| {
181 LinkerError::InvalidPackageModuleSurface {
182 package: library.package.name.to_string(),
183 reason: err.to_string(),
184 }
185 })?;
186
187 let static_library = matches!(library.linkage, Linkage::Static).then(|| library.clone());
188 let result = match self.libraries.entry(library_interface_digest) {
189 Entry::Vacant(entry) => {
190 entry.insert(library);
191 self.link_assembled_modules(module_infos)
192 },
193 Entry::Occupied(mut entry) => {
194 let prev = entry.get_mut();
195
196 if matches!(prev.linkage, Linkage::Dynamic) {
199 prev.linkage = library.linkage;
200 }
201
202 Ok(())
203 },
204 };
205
206 if result.is_ok()
207 && let Some(static_library) = static_library
208 {
209 self.static_libraries
210 .entry(static_library.commitment())
211 .or_insert(static_library);
212 }
213
214 result
215 }
216
217 pub fn link_assembled_modules(
222 &mut self,
223 modules: impl IntoIterator<Item = ModuleInfo>,
224 ) -> Result<(), LinkerError> {
225 for module in modules {
226 self.link_assembled_module(module)?;
227 }
228
229 Ok(())
230 }
231
232 pub fn link_assembled_module(
237 &mut self,
238 module: ModuleInfo,
239 ) -> Result<ModuleIndex, LinkerError> {
240 log::debug!(target: "linker", "adding pre-assembled module {} to module graph", module.path());
241
242 let module_path = module.path();
243 let is_duplicate = self.find_module_index(module_path).is_some();
244 if is_duplicate {
245 return Err(LinkerError::DuplicateModule {
246 path: module_path.to_path_buf().into_boxed_path().into(),
247 });
248 }
249
250 let module_index = self.next_module_id();
251 let submodules = module.submodules().to_vec();
252 let items = module.items();
253 let mut symbols = Vec::with_capacity(items.len());
254 for (idx, item) in items {
255 let gid = module_index + idx;
256 self.callgraph.get_or_insert_node(gid);
257 match &item {
258 ItemInfo::Procedure(item) => {
259 self.register_procedure_root(gid, item.digest);
260 },
261 ItemInfo::Constant(_) | ItemInfo::Type(_) => (),
262 }
263 symbols.push(Symbol::new(
264 item.name().clone(),
265 Visibility::Public,
266 LinkStatus::Linked,
267 SymbolItem::Compiled(item.clone()),
268 ));
269 }
270
271 let link_module = LinkModule::new(
272 module_index,
273 ast::ModuleKind::Library,
274 LinkStatus::Linked,
275 ModuleSource::Mast,
276 module_path.into(),
277 )
278 .with_submodules(submodules)
279 .with_symbols(symbols);
280
281 self.modules.push(link_module);
282 Ok(module_index)
283 }
284
285 pub fn link_modules(
289 &mut self,
290 modules: impl IntoIterator<Item = Box<Module>>,
291 ) -> Result<Vec<ModuleIndex>, LinkerError> {
292 modules.into_iter().map(|mut m| self.link_module(&mut m)).collect()
293 }
294
295 pub fn link_module(&mut self, module: &mut Module) -> Result<ModuleIndex, LinkerError> {
314 log::debug!(target: "linker", "adding unprocessed module {}", module.path());
315
316 let is_duplicate = self.find_module_index(module.path()).is_some();
317 if is_duplicate {
318 return Err(LinkerError::DuplicateModule { path: module.path().into() });
319 }
320
321 let module_index = self.next_module_id();
322 let submodules = module.submodules().to_vec();
323 let mut symbols = Vec::new();
324 let imports = module.take_imports().into_iter().map(Import::new).collect::<Vec<_>>();
325 for item in module.take_items() {
326 match item {
327 ast::Item::Type(item) => {
328 let gid = module_index + ItemIndex::new(symbols.len());
329 self.callgraph.get_or_insert_node(gid);
330 symbols.push(Symbol::new(
331 item.name().clone(),
332 item.visibility(),
333 LinkStatus::Unlinked,
334 SymbolItem::Type(item),
335 ));
336 },
337 ast::Item::Constant(item) => {
338 let gid = module_index + ItemIndex::new(symbols.len());
339 self.callgraph.get_or_insert_node(gid);
340 symbols.push(Symbol::new(
341 item.name().clone(),
342 item.visibility,
343 LinkStatus::Unlinked,
344 SymbolItem::Constant(item),
345 ));
346 },
347 ast::Item::Procedure(item) => {
348 let gid = module_index + ItemIndex::new(symbols.len());
349 self.callgraph.get_or_insert_node(gid);
350 symbols.push(Symbol::new(
351 item.name().clone().into(),
352 item.visibility(),
353 LinkStatus::Unlinked,
354 SymbolItem::Procedure(RefCell::new(Box::new(item))),
355 ));
356 },
357 }
358 }
359 let link_module = LinkModule::new(
360 module_index,
361 module.kind(),
362 LinkStatus::Unlinked,
363 ModuleSource::Ast,
364 module.path().into(),
365 )
366 .with_advice_map(module.advice_map().clone())
367 .with_submodules(submodules)
368 .with_imports(imports)
369 .with_symbols(symbols);
370
371 self.modules.push(link_module);
372 Ok(module_index)
373 }
374
375 #[inline]
376 fn next_module_id(&self) -> ModuleIndex {
377 ModuleIndex::new(self.modules.len())
378 }
379}
380
381impl Linker {
384 pub(super) fn with_kernel(
388 source_manager: Arc<dyn SourceManager>,
389 kernel_package: Arc<MastPackage>,
390 ) -> Result<Self, Report> {
391 log::debug!(target: "linker", "instantiating linker with kernel package {}@{}", kernel_package.name, kernel_package.version);
392
393 let mut linker = Self::new(source_manager);
394 linker.link_with_kernel(kernel_package)?;
395
396 Ok(linker)
397 }
398
399 pub(super) fn link_with_kernel(
407 &mut self,
408 kernel_package: Arc<MastPackage>,
409 ) -> 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()?;
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_infos = kernel_package.try_module_infos().map_err(|err| {
424 LinkerError::InvalidPackageModuleSurface {
425 package: kernel_package.name.to_string(),
426 reason: err.to_string(),
427 }
428 })?;
429 for module_info in module_infos {
430 let is_kernel_module = module_info.path().is_kernel_path();
431 let module_index = self.link_assembled_module(module_info)?;
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) -> &Kernel {
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 libraries(&self) -> impl Iterator<Item = &LinkLibrary> {
729 self.libraries.values()
730 }
731
732 pub fn static_libraries(&self) -> impl Iterator<Item = &LinkLibrary> {
734 self.static_libraries.values()
735 }
736
737 pub fn topological_sort_from_root(
739 &self,
740 caller: GlobalItemIndex,
741 ) -> Result<Vec<GlobalItemIndex>, CycleError> {
742 self.callgraph.toposort_caller(caller)
743 }
744
745 pub fn get_procedure_index_by_digest(
750 &self,
751 procedure_digest: &Word,
752 ) -> Option<GlobalItemIndex> {
753 self.procedures_by_mast_root.get(procedure_digest).map(|indices| indices[0])
754 }
755
756 pub fn conflicting_dynamic_procedure_export_root(
759 &self,
760 source_library_commitment: Word,
761 mast_root: Word,
762 selected_root_id: MastNodeId,
763 ) -> Option<MastNodeId> {
764 let library = self.libraries.get(&source_library_commitment)?;
765 if !matches!(library.linkage, Linkage::Dynamic) {
766 return None;
767 }
768
769 library
770 .module_infos()
771 .ok()?
772 .into_iter()
773 .flat_map(|module| {
774 module
775 .procedures()
776 .filter_map(|(_, proc)| {
777 (proc.digest == mast_root).then(|| proc.source_root_id()).flatten()
778 })
779 .collect::<Vec<_>>()
780 })
781 .find(|&root_id| root_id != selected_root_id)
782 }
783
784 pub fn resolve_invoke_target(
786 &self,
787 caller: &SymbolResolutionContext,
788 target: &InvocationTarget,
789 ) -> Result<SymbolResolution, LinkerError> {
790 let namespaces = NamespaceGraph::build(self)?;
791 let imports = namespaces.resolve_imports(self)?;
792 let resolver = SymbolResolver::with_namespaces(self, &namespaces, &imports);
793 resolver.resolve_invoke_target(caller, target)
794 }
795
796 pub fn resolve_path(
798 &self,
799 caller: &SymbolResolutionContext,
800 path: &Path,
801 ) -> Result<SymbolResolution, LinkerError> {
802 let namespaces = NamespaceGraph::build(self)?;
803 let imports = namespaces.resolve_imports(self)?;
804 let resolver = SymbolResolver::with_namespaces(self, &namespaces, &imports);
805 resolver.resolve_path(caller, Span::new(caller.span, path))
806 }
807
808 pub(super) fn resolve_signature(
810 &self,
811 gid: GlobalItemIndex,
812 ) -> Result<Option<Arc<types::FunctionType>>, LinkerError> {
813 match self[gid].item() {
814 SymbolItem::Compiled(ItemInfo::Procedure(proc)) => Ok(proc.signature.clone()),
815 SymbolItem::Procedure(proc) => {
816 let proc = proc.borrow();
817 match proc.signature() {
818 Some(ty) => self.translate_function_type(gid.module, ty).map(Some),
819 None => Ok(None),
820 }
821 },
822 SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
823 panic!("procedure index unexpectedly refers to non-procedure item")
824 },
825 }
826 }
827
828 fn translate_function_type(
829 &self,
830 module_index: ModuleIndex,
831 ty: &ast::FunctionType,
832 ) -> Result<Arc<types::FunctionType>, LinkerError> {
833 use miden_assembly_syntax::ast::TypeResolver;
834
835 let cc = ty.cc;
836 let mut args = Vec::with_capacity(ty.args.len());
837
838 let symbol_resolver = SymbolResolver::new(self);
839 let mut cache = ResolverCache::default();
840 let mut resolver = Resolver {
841 resolver: &symbol_resolver,
842 cache: &mut cache,
843 current_module: module_index,
844 };
845 for arg in ty.args.iter() {
846 if let Some(arg) = resolver.resolve(arg)? {
847 args.push(arg);
848 } else {
849 let span = arg.span();
850 return Err(LinkerError::UndefinedType {
851 span,
852 source_file: self.source_manager.get(span.source_id()).ok(),
853 });
854 }
855 }
856 let mut results = Vec::with_capacity(ty.results.len());
857 for result in ty.results.iter() {
858 if let Some(result) = resolver.resolve(result)? {
859 results.push(result);
860 } else {
861 let span = result.span();
862 return Err(LinkerError::UndefinedType {
863 span,
864 source_file: self.source_manager.get(span.source_id()).ok(),
865 });
866 }
867 }
868 Ok(Arc::new(types::FunctionType::new(cc, args, results)))
869 }
870
871 pub(super) fn resolve_attributes(&self, gid: GlobalItemIndex) -> AttributeSet {
873 match self[gid].item() {
874 SymbolItem::Compiled(ItemInfo::Procedure(proc)) => proc.attributes.clone(),
875 SymbolItem::Procedure(proc) => {
876 let proc = proc.borrow();
877 proc.attributes().clone()
878 },
879 SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
880 panic!("procedure index unexpectedly refers to non-procedure item")
881 },
882 }
883 }
884
885 pub(super) fn resolve_type(
887 &self,
888 span: SourceSpan,
889 gid: GlobalItemIndex,
890 ) -> Result<types::Type, LinkerError> {
891 use miden_assembly_syntax::ast::TypeResolver;
892
893 let symbol_resolver = SymbolResolver::new(self);
894 let mut cache = ResolverCache::default();
895 let mut resolver = Resolver {
896 cache: &mut cache,
897 resolver: &symbol_resolver,
898 current_module: gid.module,
899 };
900
901 resolver.get_type(span, gid)
902 }
903
904 pub(crate) fn register_procedure_root(
913 &mut self,
914 id: GlobalItemIndex,
915 procedure_mast_root: Word,
916 ) {
917 use alloc::collections::btree_map::Entry;
918 match self.procedures_by_mast_root.entry(procedure_mast_root) {
919 Entry::Occupied(ref mut entry) => {
920 let prev_id = entry.get()[0];
921 if prev_id != id {
922 entry.get_mut().push(id);
924 }
925 },
926 Entry::Vacant(entry) => {
927 entry.insert(smallvec![id]);
928 },
929 }
930 }
931
932 pub fn find_module_index(&self, path: &Path) -> Option<ModuleIndex> {
934 self.modules.iter().position(|m| path == m.path()).map(ModuleIndex::new)
935 }
936
937 pub fn find_module(&self, path: &Path) -> Option<&LinkModule> {
939 self.modules.iter().find(|m| path == m.path())
940 }
941}
942
943impl Linker {
945 pub(super) fn const_eval(
947 &self,
948 gid: GlobalItemIndex,
949 expr: &ast::ConstantExpr,
950 cache: &mut ResolverCache,
951 ) -> Result<ast::ConstantValue, LinkerError> {
952 let symbol_resolver = SymbolResolver::new(self);
953 let mut resolver = Resolver {
954 resolver: &symbol_resolver,
955 cache,
956 current_module: gid.module,
957 };
958
959 ast::constants::eval::expr(expr, &mut resolver).map(|expr| expr.expect_value())
960 }
961}
962
963impl Index<ModuleIndex> for Linker {
964 type Output = LinkModule;
965
966 fn index(&self, index: ModuleIndex) -> &Self::Output {
967 &self.modules[index.as_usize()]
968 }
969}
970
971impl Index<GlobalItemIndex> for Linker {
972 type Output = Symbol;
973
974 fn index(&self, index: GlobalItemIndex) -> &Self::Output {
975 &self.modules[index.module.as_usize()][index.index]
976 }
977}
978
979#[cfg(test)]
980mod tests {
981 use std::{
982 panic::{AssertUnwindSafe, catch_unwind},
983 string::String,
984 sync::Arc,
985 };
986
987 use miden_assembly_syntax::{
988 ast::{
989 Ident, InvocationTarget, InvokeKind, ItemIndex, Path, SymbolResolutionError,
990 Visibility, types,
991 },
992 debuginfo::{SourceSpan, Span},
993 module::{ItemInfo, TypeInfo},
994 };
995 use miden_core::Felt;
996
997 use super::*;
998 use crate::{
999 Assembler,
1000 ast::Module,
1001 testing::{TestContext, source_file},
1002 };
1003
1004 #[test]
1005 fn failed_kernel_link_restores_kernel_state() {
1006 let context = TestContext::default();
1007 let source_manager = context.source_manager();
1008 let kernel_source = r#"
1009 pub proc a
1010 call.b
1011 end
1012
1013 proc b
1014 call.a
1015 end
1016 "#;
1017
1018 let userspace = context
1019 .parse_module(source_file!(
1020 &context,
1021 r#"
1022 namespace userspace
1023
1024 pub proc helper
1025 push.1
1026 end
1027 "#
1028 ))
1029 .expect("userspace module parsing must succeed");
1030
1031 let mut linker = Linker::new(source_manager);
1032 let userspace_index = linker
1033 .link([userspace], None)
1034 .expect("userspace module must link successfully")
1035 .into_iter()
1036 .next()
1037 .expect("linked module index must be returned");
1038
1039 let first_err = linker
1040 .link_kernel(
1041 context
1042 .parse_kernel(source_file!(&context, kernel_source))
1043 .expect("kernel parsing must succeed"),
1044 None,
1045 )
1046 .expect_err("expected cyclic kernel to be rejected");
1047
1048 assert!(first_err.to_string().contains("found a cycle in the call graph"));
1049 assert!(!linker.has_nonempty_kernel(), "failed kernel link must not leave a kernel set");
1050 assert_eq!(linker[userspace_index].kind(), ast::ModuleKind::Library);
1051
1052 let second_err = linker
1053 .link_kernel(
1054 context
1055 .parse_kernel(source_file!(&context, kernel_source))
1056 .expect("kernel parsing must succeed"),
1057 None,
1058 )
1059 .expect_err("expected cyclic kernel retry to be rejected");
1060 assert!(second_err.to_string().contains("found a cycle in the call graph"));
1061 assert!(!second_err.to_string().contains("duplicate module"));
1062
1063 let syscall_context = SymbolResolutionContext {
1064 span: SourceSpan::UNKNOWN,
1065 module: userspace_index,
1066 kind: Some(InvokeKind::SysCall),
1067 };
1068 let err = linker
1069 .resolve_invoke_target(
1070 &syscall_context,
1071 &InvocationTarget::Symbol(Ident::new("a").expect("valid identifier")),
1072 )
1073 .expect_err("expected syscall without a linked kernel to be rejected");
1074 assert!(matches!(err, LinkerError::InvalidSysCallTarget { .. }));
1075 }
1076
1077 #[test]
1078 fn link_library_keeps_same_interface_libraries_with_distinct_forest_commitments() {
1079 let context = TestContext::default();
1080 let module = context
1081 .parse_module(source_file!(
1082 &context,
1083 r#"
1084 namespace lib
1085
1086 pub proc foo
1087 push.1
1088 end
1089 "#
1090 ))
1091 .expect("library module should parse");
1092 let package: Arc<MastPackage> = Assembler::new(context.source_manager())
1093 .assemble_library("lib", module, None::<Box<Module>>)
1094 .expect("library should assemble")
1095 .into();
1096 let with_advice = Arc::new(package.as_ref().clone().with_advice_map(AdviceMap::from_iter(
1097 [(Word::from([1_u32, 2, 3, 4]), vec![Felt::from_u32(5)])],
1098 )));
1099
1100 assert_ne!(package.digest(), with_advice.digest());
1101 assert_eq!(package.interface_digest().unwrap(), with_advice.interface_digest().unwrap());
1102 assert_ne!(package.mast_forest().commitment(), with_advice.mast_forest().commitment());
1103
1104 let mut linker = Linker::new(context.source_manager());
1105 linker
1106 .link_library(LinkLibrary::from_package(package).with_linkage(Linkage::Static))
1107 .expect("first library should link");
1108 linker
1109 .link_library(LinkLibrary::from_package(with_advice).with_linkage(Linkage::Static))
1110 .expect("same public interface with distinct forest commitment should link");
1111
1112 assert_eq!(linker.libraries().count(), 1);
1113 assert_eq!(linker.static_libraries().count(), 2);
1114 }
1115
1116 #[test]
1117 fn oversized_link_module_resolution_returns_structured_error() {
1118 let context = TestContext::default();
1119 let mut linker = Linker::new(context.source_manager());
1120 let module_id = ModuleIndex::new(0);
1121 let path = Arc::<Path>::from(Path::new("::m::huge"));
1122 let mut symbols = Vec::with_capacity(ItemIndex::MAX_ITEMS + 1);
1123
1124 for i in 0..=ItemIndex::MAX_ITEMS {
1125 let name = Ident::new(format!("a{i}")).expect("valid identifier");
1126 symbols.push(Symbol::new(
1127 name.clone(),
1128 Visibility::Private,
1129 LinkStatus::Unlinked,
1130 SymbolItem::Compiled(ItemInfo::Type(TypeInfo { name, ty: types::Type::Felt })),
1131 ));
1132 }
1133
1134 linker.modules.push(
1135 LinkModule::new(
1136 module_id,
1137 ast::ModuleKind::Library,
1138 LinkStatus::Unlinked,
1139 ModuleSource::Mast,
1140 path,
1141 )
1142 .with_symbols(symbols),
1143 );
1144
1145 let result = catch_unwind(AssertUnwindSafe(|| {
1146 linker[module_id].resolve(Span::unknown("a0"), &SymbolResolver::new(&linker))
1147 }));
1148
1149 let result = match result {
1150 Ok(result) => result,
1151 Err(panic) => {
1152 let message = panic
1153 .downcast_ref::<&str>()
1154 .copied()
1155 .or_else(|| panic.downcast_ref::<String>().map(String::as_str))
1156 .expect("panic payload should be a string");
1157 panic!("expected graceful error, got panic: {message}");
1158 },
1159 };
1160
1161 assert!(matches!(
1162 result,
1163 Err(err) if matches!(*err, SymbolResolutionError::TooManyItemsInModule { .. })
1164 ));
1165 }
1166}