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