1pub(super) mod debuginfo;
2mod error;
3mod product;
4
5use alloc::{boxed::Box, collections::BTreeMap, string::ToString, sync::Arc, vec::Vec};
6
7use debuginfo::DebugInfoSections;
8use miden_assembly_syntax::{
9 KernelLibrary, Library, MAX_REPEAT_COUNT, Parse, ParseOptions, SemanticAnalysisError,
10 ast::{
11 self, Ident, InvocationTarget, InvokeKind, ItemIndex, ModuleKind, SymbolResolution,
12 Visibility, types::FunctionType,
13 },
14 debuginfo::{DefaultSourceManager, SourceManager, SourceSpan, Spanned},
15 diagnostics::{IntoDiagnostic, RelatedLabel, Report},
16 library::{ConstantExport, ItemInfo, LibraryExport, ProcedureExport, TypeExport},
17};
18use miden_core::{
19 Word,
20 mast::{
21 DecoratorId, LoopNodeBuilder, MastForestContributor, MastNodeExt, MastNodeId,
22 SplitNodeBuilder,
23 },
24 operations::{AssemblyOp, Operation},
25 program::{Kernel, Program},
26};
27use miden_mast_package::PackageManifest;
28use miden_project::{Linkage, TargetType};
29
30use self::{error::AssemblerError, product::AssemblyProduct};
31use crate::{
32 GlobalItemIndex, ModuleIndex, Procedure, ProcedureContext,
33 ast::Path,
34 basic_block_builder::{BasicBlockBuilder, BasicBlockOrDecorators},
35 fmp::{fmp_end_frame_sequence, fmp_initialization_sequence, fmp_start_frame_sequence},
36 linker::{LinkLibrary, Linker, LinkerError, SymbolItem, SymbolResolutionContext},
37 mast_forest_builder::MastForestBuilder,
38};
39
40pub(crate) const MAX_CONTROL_FLOW_NESTING: usize = 256;
45
46#[derive(Clone)]
95pub struct Assembler {
96 source_manager: Arc<dyn SourceManager>,
98 linker: Box<Linker>,
100 pub(super) debug_info: DebugInfoSections,
102 warnings_as_errors: bool,
104 pub(super) emit_debug_info: bool,
106 pub(super) trim_paths: bool,
108}
109
110impl Default for Assembler {
111 fn default() -> Self {
112 let source_manager = Arc::new(DefaultSourceManager::default());
113 let linker = Box::new(Linker::new(source_manager.clone()));
114 Self {
115 source_manager,
116 linker,
117 debug_info: Default::default(),
118 warnings_as_errors: false,
119 emit_debug_info: true,
120 trim_paths: false,
121 }
122 }
123}
124
125impl Assembler {
128 pub fn new(source_manager: Arc<dyn SourceManager>) -> Self {
130 let linker = Box::new(Linker::new(source_manager.clone()));
131 Self {
132 source_manager,
133 linker,
134 debug_info: Default::default(),
135 warnings_as_errors: false,
136 emit_debug_info: true,
137 trim_paths: false,
138 }
139 }
140
141 pub fn with_kernel(source_manager: Arc<dyn SourceManager>, kernel_lib: KernelLibrary) -> Self {
143 let (kernel, kernel_module, _) = kernel_lib.into_parts();
144 let linker = Box::new(Linker::with_kernel(source_manager.clone(), kernel, kernel_module));
145 Self {
146 source_manager,
147 linker,
148 ..Default::default()
149 }
150 }
151
152 pub fn with_warnings_as_errors(mut self, yes: bool) -> Self {
156 self.warnings_as_errors = yes;
157 self
158 }
159
160 #[cfg(feature = "std")]
161 pub(crate) fn with_emit_debug_info(mut self, yes: bool) -> Self {
162 self.emit_debug_info = yes;
163 self
164 }
165
166 #[cfg(feature = "std")]
167 pub(crate) fn with_trim_paths(mut self, yes: bool) -> Self {
168 self.trim_paths = yes;
169 self
170 }
171
172 pub(crate) fn invalid_invoke_target_report(
173 &self,
174 kind: InvokeKind,
175 callee: &InvocationTarget,
176 caller: GlobalItemIndex,
177 ) -> Report {
178 let span = callee.span();
179 let source_file = self.source_manager.get(span.source_id()).ok();
180 let context = SymbolResolutionContext {
181 span,
182 module: caller.module,
183 kind: Some(kind),
184 };
185
186 let path = match self.linker.resolve_invoke_target(&context, callee) {
187 Ok(
188 SymbolResolution::Exact { path, .. }
189 | SymbolResolution::Module { path, .. }
190 | SymbolResolution::External(path),
191 ) => Some(path.into_inner()),
192 Ok(SymbolResolution::Local(_) | SymbolResolution::MastRoot(_)) => None,
193 Err(err) => return Report::new(err),
194 }
195 .or_else(|| match callee {
196 InvocationTarget::Symbol(symbol) => Some(Path::from_ident(symbol).into_owned().into()),
197 InvocationTarget::Path(path) => Some(path.clone().into_inner()),
198 InvocationTarget::MastRoot(_) => None,
199 });
200
201 match path {
202 Some(path) => Report::new(LinkerError::InvalidInvokeTarget { span, source_file, path }),
203 None => Report::msg("invalid procedure reference: target is not a procedure"),
204 }
205 }
206}
207
208impl Assembler {
211 #[inline]
215 pub fn compile_and_statically_link(&mut self, module: impl Parse) -> Result<&mut Self, Report> {
216 self.compile_and_statically_link_all([module])
217 }
218
219 pub fn compile_and_statically_link_all(
224 &mut self,
225 modules: impl IntoIterator<Item = impl Parse>,
226 ) -> Result<&mut Self, Report> {
227 let modules = modules
228 .into_iter()
229 .map(|module| {
230 module.parse_with_options(
231 self.source_manager.clone(),
232 ParseOptions {
233 warnings_as_errors: self.warnings_as_errors,
234 ..ParseOptions::for_library()
235 },
236 )
237 })
238 .collect::<Result<Vec<_>, Report>>()?;
239
240 self.linker.link_modules(modules)?;
241
242 Ok(self)
243 }
244
245 #[cfg(feature = "std")]
277 pub fn compile_and_statically_link_from_dir(
278 &mut self,
279 dir: impl AsRef<std::path::Path>,
280 namespace: impl AsRef<Path>,
281 ) -> Result<(), Report> {
282 use miden_assembly_syntax::parser;
283
284 let namespace = namespace.as_ref();
285 let modules = parser::read_modules_from_dir(
286 dir,
287 namespace,
288 self.source_manager.clone(),
289 self.warnings_as_errors,
290 )?;
291 self.linker.link_modules(modules)?;
292 Ok(())
293 }
294
295 pub fn link_library(
309 &mut self,
310 library: impl AsRef<Library>,
311 linkage: Linkage,
312 ) -> Result<(), Report> {
313 self.linker
314 .link_library(LinkLibrary::from_library(library.as_ref()).with_linkage(linkage))
315 .map_err(Report::from)
316 }
317
318 pub fn link_dynamic_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> {
344 let library = LinkLibrary::from_library(library.as_ref()).with_linkage(Linkage::Dynamic);
345 self.linker.link_library(library).map_err(Report::from)
346 }
347
348 pub fn with_dynamic_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
352 self.link_dynamic_library(library)?;
353 Ok(self)
354 }
355
356 pub fn link_static_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> {
365 let library = LinkLibrary::from_library(library.as_ref()).with_linkage(Linkage::Static);
366 self.linker.link_library(library).map_err(Report::from)
367 }
368
369 pub fn with_static_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
373 self.link_static_library(library)?;
374 Ok(self)
375 }
376
377 pub fn link_package(
379 &mut self,
380 package: Arc<miden_mast_package::Package>,
381 linkage: Linkage,
382 ) -> Result<(), Report> {
383 match package.kind {
384 TargetType::Kernel => {
385 if !self.kernel().is_empty() {
386 return Err(Report::msg(format!(
387 "duplicate kernels present in the dependency graph: '{}@{}' conflicts with another kernel we've already linked",
388 &package.name, &package.version
389 )));
390 }
391
392 let kernel_module = package.kernel_module_info()?;
393 let kernel = package.to_kernel()?;
394 self.linker.link_with_kernel(kernel, kernel_module)?;
395 Ok(())
396 },
397 TargetType::Executable => {
398 Err(Report::msg("cannot add executable packages to an assembler"))
399 },
400 _ => {
401 self.linker
402 .link_library(LinkLibrary::from_package(package).with_linkage(linkage))?;
403 Ok(())
404 },
405 }
406 }
407}
408
409impl Assembler {
412 pub fn warnings_as_errors(&self) -> bool {
414 self.warnings_as_errors
415 }
416
417 pub fn kernel(&self) -> &Kernel {
421 self.linker.kernel()
422 }
423
424 #[cfg(any(feature = "std", all(test, feature = "std")))]
425 pub(crate) fn source_manager(&self) -> Arc<dyn SourceManager> {
426 self.source_manager.clone()
427 }
428
429 #[cfg(any(test, feature = "testing"))]
430 #[doc(hidden)]
431 pub fn linker(&self) -> &Linker {
432 &self.linker
433 }
434}
435
436impl Assembler {
439 pub fn assemble_library(
445 self,
446 modules: impl IntoIterator<Item = impl Parse>,
447 ) -> Result<Arc<Library>, Report> {
448 let modules = modules
449 .into_iter()
450 .map(|module| {
451 module.parse_with_options(
452 self.source_manager.clone(),
453 ParseOptions {
454 warnings_as_errors: self.warnings_as_errors,
455 ..ParseOptions::for_library()
456 },
457 )
458 })
459 .collect::<Result<Vec<_>, Report>>()?;
460
461 Ok(self.assemble_library_modules(modules, TargetType::Library)?.into_artifact())
462 }
463
464 #[cfg(feature = "std")]
499 pub fn assemble_library_from_dir(
500 self,
501 dir: impl AsRef<std::path::Path>,
502 namespace: impl AsRef<Path>,
503 ) -> Result<Arc<Library>, Report> {
504 use miden_assembly_syntax::parser;
505
506 let dir = dir.as_ref();
507 let namespace = namespace.as_ref();
508
509 let source_manager = self.source_manager.clone();
510 let modules =
511 parser::read_modules_from_dir(dir, namespace, source_manager, self.warnings_as_errors)?;
512 self.assemble_library(modules)
513 }
514
515 pub fn assemble_kernel(self, module: impl Parse) -> Result<KernelLibrary, Report> {
521 let module = module.parse_with_options(
522 self.source_manager.clone(),
523 ParseOptions {
524 path: Some(Path::kernel_path().into()),
525 warnings_as_errors: self.warnings_as_errors,
526 ..ParseOptions::for_kernel()
527 },
528 )?;
529
530 self.assemble_kernel_module(module)?.into_kernel_library()
531 }
532
533 #[cfg(feature = "std")]
547 pub fn assemble_kernel_from_dir(
548 mut self,
549 sys_module_path: impl AsRef<std::path::Path>,
550 lib_dir: Option<impl AsRef<std::path::Path>>,
551 ) -> Result<KernelLibrary, Report> {
552 if let Some(lib_dir) = lib_dir {
554 self.compile_and_statically_link_from_dir(lib_dir, Path::kernel_path())?;
555 }
556
557 self.assemble_kernel(sys_module_path.as_ref())
558 }
559
560 fn assemble_library_product(
562 mut self,
563 module_indices: &[ModuleIndex],
564 kind: TargetType,
565 ) -> Result<AssemblyProduct, Report> {
566 let staticlibs = self.linker.libraries().filter_map(|lib| {
567 if matches!(lib.linkage, Linkage::Static) {
568 Some(lib.mast.as_ref())
569 } else {
570 None
571 }
572 });
573 let mut mast_forest_builder = MastForestBuilder::new(staticlibs)?;
574 mast_forest_builder.set_emit_debug_info(self.emit_debug_info);
575 let mut exports = {
576 let mut exports = BTreeMap::new();
577
578 for module_idx in module_indices.iter().copied() {
579 let module = &self.linker[module_idx];
580
581 if let Some(advice_map) = module.advice_map() {
582 mast_forest_builder.merge_advice_map(advice_map)?;
583 }
584
585 let module_kind = module.kind();
586 let module_path = module.path().clone();
587 for index in 0..module.symbols().len() {
588 let index = ItemIndex::new(index);
589 let gid = module_idx + index;
590
591 let path: Arc<Path> = {
592 let symbol = &self.linker[gid];
593 if !symbol.visibility().is_public() {
594 continue;
595 }
596 module_path
597 .join(symbol.name())
598 .canonicalize()
599 .into_diagnostic()?
600 .into_boxed_path()
601 .into()
602 };
603 let export = self.export_symbol(
604 gid,
605 module_kind,
606 path.clone(),
607 &mut mast_forest_builder,
608 )?;
609 if exports.insert(path.clone(), export).is_some() {
610 return Err(Report::new(AssemblerError::DuplicateExportPath { path }));
611 }
612 }
613 }
614
615 exports
616 };
617
618 let (mast_forest, id_remappings) = mast_forest_builder.build();
619 for (_proc_name, export) in exports.iter_mut() {
620 match export {
621 LibraryExport::Procedure(export) => {
622 if let Some(&new_node_id) = id_remappings.get(&export.node) {
623 export.node = new_node_id;
624 }
625 },
626 LibraryExport::Constant(_) | LibraryExport::Type(_) => (),
627 }
628 }
629
630 self.finish_library_product(mast_forest, exports, kind)
631 }
632
633 fn export_symbol(
639 &mut self,
640 gid: GlobalItemIndex,
641 module_kind: ModuleKind,
642 symbol_path: Arc<Path>,
643 mast_forest_builder: &mut MastForestBuilder,
644 ) -> Result<LibraryExport, Report> {
645 log::trace!(target: "assembler::export_symbol", "exporting {} {symbol_path}", match self.linker[gid].item() {
646 SymbolItem::Compiled(ItemInfo::Procedure(_)) => "compiled procedure",
647 SymbolItem::Compiled(ItemInfo::Constant(_)) => "compiled constant",
648 SymbolItem::Compiled(ItemInfo::Type(_)) => "compiled type",
649 SymbolItem::Procedure(_) => "procedure",
650 SymbolItem::Constant(_) => "constant",
651 SymbolItem::Type(_) => "type",
652 SymbolItem::Alias { .. } => "alias",
653 });
654 let mut cache = crate::linker::ResolverCache::default();
655 let export = match self.linker[gid].item() {
656 SymbolItem::Compiled(ItemInfo::Procedure(item)) => {
657 let resolved = match mast_forest_builder.get_procedure(gid) {
658 Some(proc) => ResolvedProcedure {
659 node: proc.body_node_id(),
660 signature: proc.signature(),
661 },
662 None => {
665 let node = self.ensure_valid_procedure_mast_root(
666 InvokeKind::ProcRef,
667 SourceSpan::UNKNOWN,
668 item.digest,
669 mast_forest_builder,
670 )?;
671 ResolvedProcedure { node, signature: item.signature.clone() }
672 },
673 };
674 let digest = item.digest;
675 let ResolvedProcedure { node, signature } = resolved;
676 let attributes = item.attributes.clone();
677 let pctx = ProcedureContext::new(
678 gid,
679 false,
680 symbol_path.clone(),
681 Visibility::Public,
682 signature.clone(),
683 module_kind.is_kernel(),
684 self.source_manager.clone(),
685 );
686
687 let procedure = pctx.into_procedure(digest, node);
688 self.linker.register_procedure_root(gid, digest)?;
689 mast_forest_builder.insert_procedure(gid, procedure)?;
690 LibraryExport::Procedure(ProcedureExport {
691 node,
692 path: symbol_path,
693 signature: signature.map(|sig| (*sig).clone()),
694 attributes,
695 })
696 },
697 SymbolItem::Compiled(ItemInfo::Constant(item)) => {
698 LibraryExport::Constant(ConstantExport {
699 path: symbol_path,
700 value: item.value.clone(),
701 })
702 },
703 SymbolItem::Compiled(ItemInfo::Type(item)) => {
704 LibraryExport::Type(TypeExport { path: symbol_path, ty: item.ty.clone() })
705 },
706 SymbolItem::Procedure(_) => {
707 self.compile_subgraph(SubgraphRoot::not_as_entrypoint(gid), mast_forest_builder)?;
708 let node = mast_forest_builder
709 .get_procedure(gid)
710 .expect("compilation succeeded but root not found in cache")
711 .body_node_id();
712 let signature = self.linker.resolve_signature(gid)?;
713 let attributes = self.linker.resolve_attributes(gid)?;
714 LibraryExport::Procedure(ProcedureExport {
715 node,
716 path: symbol_path,
717 signature: signature.map(Arc::unwrap_or_clone),
718 attributes,
719 })
720 },
721 SymbolItem::Constant(item) => {
722 let value = self.linker.const_eval(gid, &item.value, &mut cache)?;
724
725 LibraryExport::Constant(ConstantExport { path: symbol_path, value })
726 },
727 SymbolItem::Type(item) => {
728 let ty = self.linker.resolve_type(item.span(), gid)?;
729 LibraryExport::Type(TypeExport { path: symbol_path, ty })
730 },
731
732 SymbolItem::Alias { alias, resolved } => {
733 if let Some(resolved) = resolved.get() {
734 return self.export_symbol(
735 resolved,
736 module_kind,
737 symbol_path,
738 mast_forest_builder,
739 );
740 }
741
742 let Some(ResolvedProcedure { node, signature }) = self.resolve_target(
743 InvokeKind::ProcRef,
744 &alias.target().into(),
745 gid,
746 mast_forest_builder,
747 )?
748 else {
749 return Err(self.unresolved_alias_report("export", &symbol_path, alias));
750 };
751
752 let digest = mast_forest_builder
753 .get_mast_node(node)
754 .expect("resolved alias export node must exist")
755 .digest();
756 let pctx = ProcedureContext::new(
757 gid,
758 false,
759 symbol_path.clone(),
760 Visibility::Public,
761 signature.clone(),
762 module_kind.is_kernel(),
763 self.source_manager.clone(),
764 );
765 let procedure = pctx.into_procedure(digest, node);
766 self.linker.register_procedure_root(gid, digest)?;
767 mast_forest_builder.insert_procedure(gid, procedure)?;
768
769 return Ok(LibraryExport::Procedure(ProcedureExport {
770 node,
771 path: symbol_path,
772 signature: signature.map(Arc::unwrap_or_clone),
773 attributes: Default::default(),
774 }));
775 },
776 };
777
778 Ok(export)
779 }
780
781 pub fn assemble_program(self, source: impl Parse) -> Result<Program, Report> {
789 let options = ParseOptions {
790 kind: ModuleKind::Executable,
791 warnings_as_errors: self.warnings_as_errors,
792 path: Some(Path::exec_path().into()),
793 };
794
795 let program = source.parse_with_options(self.source_manager.clone(), options)?;
796 assert!(program.is_executable());
797
798 self.assemble_executable_modules(program, [])?.into_program()
799 }
800
801 pub(crate) fn assemble_library_modules(
802 mut self,
803 modules: impl IntoIterator<Item = Box<ast::Module>>,
804 kind: TargetType,
805 ) -> Result<AssemblyProduct, Report> {
806 let module_indices = self.linker.link(modules)?;
807 self.assemble_library_product(&module_indices, kind)
808 }
809
810 pub(crate) fn assemble_kernel_module(
811 mut self,
812 module: Box<ast::Module>,
813 ) -> Result<AssemblyProduct, Report> {
814 let module_indices = self.linker.link_kernel(module)?;
815 self.assemble_library_product(&module_indices, TargetType::Kernel)
816 }
817
818 pub(crate) fn assemble_executable_modules(
819 mut self,
820 program: Box<ast::Module>,
821 support_modules: impl IntoIterator<Item = Box<ast::Module>>,
822 ) -> Result<AssemblyProduct, Report> {
823 self.linker.link_modules(support_modules)?;
824
825 let module_index = self.linker.link([program])?[0];
827
828 let entrypoint = self.linker[module_index]
831 .symbols()
832 .position(|symbol| symbol.name().as_str() == Ident::MAIN)
833 .map(|index| module_index + ItemIndex::new(index))
834 .ok_or(SemanticAnalysisError::MissingEntrypoint)?;
835
836 let staticlibs = self.linker.libraries().filter_map(|lib| {
838 if matches!(lib.linkage, Linkage::Static) {
839 Some(lib.mast.as_ref())
840 } else {
841 None
842 }
843 });
844 let mut mast_forest_builder = MastForestBuilder::new(staticlibs)?;
845 mast_forest_builder.set_emit_debug_info(self.emit_debug_info);
846
847 if let Some(advice_map) = self.linker[module_index].advice_map() {
848 mast_forest_builder.merge_advice_map(advice_map)?;
849 }
850
851 self.compile_subgraph(SubgraphRoot::with_entrypoint(entrypoint), &mut mast_forest_builder)?;
852 let entry_node_id = mast_forest_builder
853 .get_procedure(entrypoint)
854 .expect("compilation succeeded but root not found in cache")
855 .body_node_id();
856
857 let (mast_forest, id_remappings) = mast_forest_builder.build();
859 let entry_node_id = *id_remappings.get(&entry_node_id).unwrap_or(&entry_node_id);
860
861 self.finish_program_product(mast_forest, entry_node_id, self.linker.kernel().clone())
862 }
863
864 fn finish_library_product(
865 &self,
866 mut mast_forest: miden_core::mast::MastForest,
867 exports: BTreeMap<Arc<Path>, LibraryExport>,
868 kind: TargetType,
869 ) -> Result<AssemblyProduct, Report> {
870 self.apply_debug_options(&mut mast_forest);
871
872 let library = Library::new(Arc::new(mast_forest), exports)?;
873 let manifest = PackageManifest::from_library(&library);
874 let debug_info = self.emit_debug_info.then(|| {
875 #[cfg_attr(not(feature = "std"), expect(unused_mut))]
876 let mut debug_info = self.debug_info.clone();
877 #[cfg(feature = "std")]
878 if let Some(trimmer) = self.source_path_trimmer() {
879 debug_info.trim_paths(&trimmer);
880 }
881 debug_info
882 });
883
884 Ok(AssemblyProduct::new(kind, Arc::new(library), None, manifest, debug_info))
885 }
886
887 fn finish_program_product(
888 &self,
889 mut mast_forest: miden_core::mast::MastForest,
890 entrypoint: MastNodeId,
891 kernel: Kernel,
892 ) -> Result<AssemblyProduct, Report> {
893 self.apply_debug_options(&mut mast_forest);
894
895 let mast = Arc::new(mast_forest);
896 let entry: Arc<Path> = Path::exec_path().join(ast::ProcedureName::MAIN_PROC_NAME).into();
897 let entrypoint = LibraryExport::Procedure(ProcedureExport {
898 node: entrypoint,
899 path: entry.clone(),
900 signature: None,
901 attributes: Default::default(),
902 });
903 let library = Arc::new(Library::new(mast, BTreeMap::from_iter([(entry, entrypoint)]))?);
904 let manifest = PackageManifest::from_library(&library);
905 let debug_info = self.emit_debug_info.then(|| {
906 #[cfg_attr(not(feature = "std"), expect(unused_mut))]
907 let mut debug_info = self.debug_info.clone();
908 #[cfg(feature = "std")]
909 if let Some(trimmer) = self.source_path_trimmer() {
910 debug_info.trim_paths(&trimmer);
911 }
912 debug_info
913 });
914
915 Ok(AssemblyProduct::new(
916 TargetType::Executable,
917 library,
918 Some(kernel),
919 manifest,
920 debug_info,
921 ))
922 }
923
924 fn apply_debug_options(&self, mast_forest: &mut miden_core::mast::MastForest) {
925 if !self.emit_debug_info {
926 mast_forest.clear_debug_info();
927 return;
928 }
929
930 if self.trim_paths {
931 #[cfg(feature = "std")]
932 if let Some(trimmer) = self.source_path_trimmer() {
933 mast_forest.debug_info_mut().rewrite_source_locations(
934 |location| trimmer.trim_location(location),
935 |location| trimmer.trim_file_line_col(location),
936 );
937 }
938 }
939 }
940
941 #[cfg(feature = "std")]
942 fn source_path_trimmer(&self) -> Option<debuginfo::SourcePathTrimmer> {
943 if !self.trim_paths {
944 return None;
945 }
946
947 std::env::current_dir().ok().map(debuginfo::SourcePathTrimmer::new)
948 }
949
950 fn compile_subgraph(
955 &mut self,
956 root: SubgraphRoot,
957 mast_forest_builder: &mut MastForestBuilder,
958 ) -> Result<(), Report> {
959 let mut worklist: Vec<GlobalItemIndex> = self
960 .linker
961 .topological_sort_from_root(root.proc_id)
962 .map_err(|cycle| {
963 let iter = cycle.into_node_ids();
964 let mut nodes = Vec::with_capacity(iter.len());
965 for node in iter {
966 let module = self.linker[node.module].path();
967 let proc = self.linker[node].name();
968 nodes.push(format!("{}", module.join(proc)));
969 }
970 LinkerError::Cycle { nodes: nodes.into() }
971 })?
972 .into_iter()
973 .filter(|&gid| {
974 matches!(
975 self.linker[gid].item(),
976 SymbolItem::Procedure(_) | SymbolItem::Alias { .. }
977 )
978 })
979 .collect();
980
981 assert!(!worklist.is_empty());
982
983 self.process_graph_worklist(&mut worklist, &root, mast_forest_builder)
984 }
985
986 fn process_graph_worklist(
988 &mut self,
989 worklist: &mut Vec<GlobalItemIndex>,
990 root: &SubgraphRoot,
991 mast_forest_builder: &mut MastForestBuilder,
992 ) -> Result<(), Report> {
993 while let Some(procedure_gid) = worklist.pop() {
996 if let Some(proc) = mast_forest_builder.get_procedure(procedure_gid) {
998 self.linker.register_procedure_root(procedure_gid, proc.mast_root())?;
999 continue;
1000 }
1001 let (module_kind, module_path) = {
1003 let module = &self.linker[procedure_gid.module];
1004 (module.kind(), module.path().clone())
1005 };
1006 match self.linker[procedure_gid].item() {
1007 SymbolItem::Procedure(proc) => {
1008 let proc = proc.borrow();
1009 let num_locals = proc.num_locals();
1010 let path = Arc::<Path>::from(module_path.join(proc.name().as_str()));
1011 let signature = self.linker.resolve_signature(procedure_gid)?;
1012 let is_program_entrypoint =
1013 root.is_program_entrypoint && root.proc_id == procedure_gid;
1014
1015 let pctx = ProcedureContext::new(
1016 procedure_gid,
1017 is_program_entrypoint,
1018 path.clone(),
1019 proc.visibility(),
1020 signature.clone(),
1021 module_kind.is_kernel(),
1022 self.source_manager.clone(),
1023 )
1024 .with_num_locals(num_locals)
1025 .with_span(proc.span());
1026
1027 let procedure = self.compile_procedure(pctx, mast_forest_builder)?;
1029 self.debug_info
1037 .register_procedure_debug_info(&procedure, self.source_manager.as_ref())?;
1038
1039 drop(proc);
1041 self.linker.register_procedure_root(procedure_gid, procedure.mast_root())?;
1042 mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
1043 },
1044 SymbolItem::Alias { alias, resolved } => {
1045 let path: Arc<Path> = module_path.join(alias.name().as_str()).into();
1046 let procedure_gid = match resolved.get() {
1047 Some(procedure_gid) => {
1048 match self.linker[procedure_gid].item() {
1049 SymbolItem::Procedure(_)
1050 | SymbolItem::Compiled(ItemInfo::Procedure(_)) => {},
1051 SymbolItem::Constant(_)
1052 | SymbolItem::Type(_)
1053 | SymbolItem::Compiled(_) => {
1054 continue;
1055 },
1056 SymbolItem::Alias { .. } => unreachable!(),
1061 }
1062 procedure_gid
1063 },
1064 None => procedure_gid,
1065 };
1066 let is_program_entrypoint = false;
1068 let mut pctx = ProcedureContext::new(
1069 procedure_gid,
1070 is_program_entrypoint,
1071 path,
1072 Visibility::Public,
1073 None,
1074 module_kind.is_kernel(),
1075 self.source_manager.clone(),
1076 )
1077 .with_span(alias.span());
1078
1079 let Some(ResolvedProcedure { node: proc_node_id, signature }) = self
1083 .resolve_target(
1084 InvokeKind::ProcRef,
1085 &alias.target().into(),
1086 procedure_gid,
1087 mast_forest_builder,
1088 )?
1089 else {
1090 continue;
1091 };
1092
1093 pctx.set_signature(signature);
1094
1095 let proc_mast_root =
1096 mast_forest_builder.get_mast_node(proc_node_id).unwrap().digest();
1097
1098 let procedure = pctx.into_procedure(proc_mast_root, proc_node_id);
1099
1100 self.linker.register_procedure_root(procedure_gid, proc_mast_root)?;
1102 mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
1103 },
1104 SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
1105 },
1107 }
1108 }
1109
1110 Ok(())
1111 }
1112
1113 fn unresolved_alias_report(
1114 &self,
1115 action: &'static str,
1116 symbol_path: &Path,
1117 alias: &ast::Alias,
1118 ) -> Report {
1119 let span = alias.target().span();
1120 let reason = match alias.target() {
1121 ast::AliasTarget::MastRoot(_) => {
1122 "this digest target does not resolve to a known procedure"
1123 },
1124 ast::AliasTarget::Path(_) => "this alias target does not resolve to a concrete item",
1125 };
1126
1127 RelatedLabel::error(format!(
1128 "unable to {action} alias '{symbol_path}' targeting '{}'",
1129 alias.target()
1130 ))
1131 .with_labeled_span(span, reason)
1132 .with_help("aliases must resolve to a concrete item before they can be used")
1133 .with_source_file(self.source_manager.get(span.source_id()).ok())
1134 .into()
1135 }
1136
1137 fn compile_procedure(
1139 &self,
1140 mut proc_ctx: ProcedureContext,
1141 mast_forest_builder: &mut MastForestBuilder,
1142 ) -> Result<Procedure, Report> {
1143 let gid = proc_ctx.id();
1145
1146 let num_locals = proc_ctx.num_locals();
1147
1148 let proc = match self.linker[gid].item() {
1149 SymbolItem::Procedure(proc) => proc.borrow(),
1150 _ => panic!("expected item to be a procedure AST"),
1151 };
1152 let body_wrapper = if proc_ctx.is_program_entrypoint() {
1153 assert!(num_locals == 0, "program entrypoint cannot have locals");
1154
1155 Some(BodyWrapper {
1156 prologue: fmp_initialization_sequence(),
1157 epilogue: Vec::new(),
1158 })
1159 } else if num_locals > 0 {
1160 Some(BodyWrapper {
1161 prologue: fmp_start_frame_sequence(num_locals),
1162 epilogue: fmp_end_frame_sequence(num_locals),
1163 })
1164 } else {
1165 None
1166 };
1167
1168 let proc_body_id =
1169 self.compile_body(proc.iter(), &mut proc_ctx, body_wrapper, mast_forest_builder, 0)?;
1170
1171 let proc_body_node = mast_forest_builder
1172 .get_mast_node(proc_body_id)
1173 .expect("no MAST node for compiled procedure");
1174 Ok(proc_ctx.into_procedure(proc_body_node.digest(), proc_body_id))
1175 }
1176
1177 fn create_asmop_decorator(
1179 &self,
1180 span: &SourceSpan,
1181 op_name: &str,
1182 proc_ctx: &ProcedureContext,
1183 ) -> AssemblyOp {
1184 let location = proc_ctx.source_manager().location(*span).ok();
1185 let context_name = proc_ctx.path().to_string();
1186 let num_cycles = 0;
1187 AssemblyOp::new(location, context_name, num_cycles, op_name.to_string())
1188 }
1189
1190 fn compile_body<'a, I>(
1191 &self,
1192 body: I,
1193 proc_ctx: &mut ProcedureContext,
1194 wrapper: Option<BodyWrapper>,
1195 mast_forest_builder: &mut MastForestBuilder,
1196 nesting_depth: usize,
1197 ) -> Result<MastNodeId, Report>
1198 where
1199 I: Iterator<Item = &'a ast::Op>,
1200 {
1201 use ast::Op;
1202
1203 let mut body_node_ids: Vec<MastNodeId> = Vec::new();
1204 let mut block_builder = BasicBlockBuilder::new(wrapper, mast_forest_builder);
1205
1206 for op in body {
1207 match op {
1208 Op::Inst(inst) => {
1209 if let Some(node_id) =
1210 self.compile_instruction(inst, &mut block_builder, proc_ctx)?
1211 {
1212 if let Some(basic_block_id) = block_builder.make_basic_block()? {
1213 body_node_ids.push(basic_block_id);
1214 } else if let Some(decorator_ids) = block_builder.drain_decorators() {
1215 block_builder
1216 .mast_forest_builder_mut()
1217 .append_before_enter(node_id, decorator_ids)
1218 .into_diagnostic()?;
1219 }
1220
1221 body_node_ids.push(node_id);
1222 }
1223 },
1224
1225 Op::If { then_blk, else_blk, span } => {
1226 if let Some(basic_block_id) = block_builder.make_basic_block()? {
1227 body_node_ids.push(basic_block_id);
1228 }
1229
1230 let next_depth = nesting_depth + 1;
1231 if next_depth > MAX_CONTROL_FLOW_NESTING {
1232 return Err(Report::new(AssemblerError::ControlFlowNestingDepthExceeded {
1233 span: *span,
1234 source_file: proc_ctx.source_manager().get(span.source_id()).ok(),
1235 max_depth: MAX_CONTROL_FLOW_NESTING,
1236 }));
1237 }
1238
1239 let then_blk = self.compile_body(
1240 then_blk.iter(),
1241 proc_ctx,
1242 None,
1243 block_builder.mast_forest_builder_mut(),
1244 next_depth,
1245 )?;
1246 let else_blk = self.compile_body(
1247 else_blk.iter(),
1248 proc_ctx,
1249 None,
1250 block_builder.mast_forest_builder_mut(),
1251 next_depth,
1252 )?;
1253
1254 let asm_op = self.create_asmop_decorator(span, "if.true", proc_ctx);
1255 let mut split_builder = SplitNodeBuilder::new([then_blk, else_blk]);
1256 if let Some(decorator_ids) = block_builder.drain_decorators() {
1257 split_builder.append_before_enter(decorator_ids);
1258 }
1259
1260 let split_node_id = block_builder
1261 .mast_forest_builder_mut()
1262 .ensure_node_with_asm_op(split_builder, asm_op)?;
1263
1264 body_node_ids.push(split_node_id);
1265 },
1266
1267 Op::Repeat { count, body, span } => {
1268 if let Some(basic_block_id) = block_builder.make_basic_block()? {
1269 body_node_ids.push(basic_block_id);
1270 }
1271
1272 let next_depth = nesting_depth + 1;
1273 if next_depth > MAX_CONTROL_FLOW_NESTING {
1274 return Err(Report::new(AssemblerError::ControlFlowNestingDepthExceeded {
1275 span: *span,
1276 source_file: proc_ctx.source_manager().get(span.source_id()).ok(),
1277 max_depth: MAX_CONTROL_FLOW_NESTING,
1278 }));
1279 }
1280
1281 let repeat_node_id = self.compile_body(
1282 body.iter(),
1283 proc_ctx,
1284 None,
1285 block_builder.mast_forest_builder_mut(),
1286 next_depth,
1287 )?;
1288
1289 let iteration_count = (*count).expect_value();
1290 if iteration_count == 0 {
1291 return Err(RelatedLabel::error("invalid repeat count")
1292 .with_help("repeat count must be greater than 0")
1293 .with_labeled_span(count.span(), "repeat count must be at least 1")
1294 .with_source_file(
1295 proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
1296 )
1297 .into());
1298 }
1299 if iteration_count > MAX_REPEAT_COUNT {
1300 return Err(RelatedLabel::error("invalid repeat count")
1301 .with_help(format!(
1302 "repeat count must be less than or equal to {MAX_REPEAT_COUNT}",
1303 ))
1304 .with_labeled_span(
1305 count.span(),
1306 format!("repeat count exceeds {MAX_REPEAT_COUNT}"),
1307 )
1308 .with_source_file(
1309 proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
1310 )
1311 .into());
1312 }
1313
1314 if let Some(decorator_ids) = block_builder.drain_decorators() {
1315 let first_repeat_builder = block_builder.mast_forest_builder()
1320 [repeat_node_id]
1321 .clone()
1322 .to_builder(block_builder.mast_forest_builder().mast_forest())
1323 .with_before_enter(decorator_ids);
1324 let first_repeat_node_id = block_builder
1325 .mast_forest_builder_mut()
1326 .ensure_node_preserving_debug_vars(
1327 first_repeat_builder,
1328 repeat_node_id,
1329 )?;
1330
1331 body_node_ids.push(first_repeat_node_id);
1332 let remaining_iterations =
1333 iteration_count.checked_sub(1).ok_or_else(|| {
1334 Report::new(
1335 RelatedLabel::error("invalid repeat count")
1336 .with_help("repeat count must be greater than 0")
1337 .with_labeled_span(
1338 count.span(),
1339 "repeat count must be at least 1",
1340 )
1341 .with_source_file(
1342 proc_ctx
1343 .source_manager()
1344 .get(proc_ctx.span().source_id())
1345 .ok(),
1346 ),
1347 )
1348 })?;
1349 for _ in 0..remaining_iterations {
1350 body_node_ids.push(repeat_node_id);
1351 }
1352 } else {
1353 for _ in 0..iteration_count {
1354 body_node_ids.push(repeat_node_id);
1355 }
1356 }
1357 },
1358
1359 Op::While { body, span } => {
1360 if let Some(basic_block_id) = block_builder.make_basic_block()? {
1361 body_node_ids.push(basic_block_id);
1362 }
1363
1364 let next_depth = nesting_depth + 1;
1365 if next_depth > MAX_CONTROL_FLOW_NESTING {
1366 return Err(Report::new(AssemblerError::ControlFlowNestingDepthExceeded {
1367 span: *span,
1368 source_file: proc_ctx.source_manager().get(span.source_id()).ok(),
1369 max_depth: MAX_CONTROL_FLOW_NESTING,
1370 }));
1371 }
1372
1373 let loop_body_node_id = self.compile_body(
1374 body.iter(),
1375 proc_ctx,
1376 None,
1377 block_builder.mast_forest_builder_mut(),
1378 next_depth,
1379 )?;
1380 let mut loop_builder = LoopNodeBuilder::new(loop_body_node_id);
1381 if let Some(decorator_ids) = block_builder.drain_decorators() {
1382 loop_builder.append_before_enter(decorator_ids);
1383 }
1384
1385 let asm_op = self.create_asmop_decorator(span, "while.true", proc_ctx);
1386 let loop_node_id = block_builder
1387 .mast_forest_builder_mut()
1388 .ensure_node_with_asm_op(loop_builder, asm_op)?;
1389
1390 body_node_ids.push(loop_node_id);
1391 },
1392 }
1393 }
1394
1395 let maybe_post_decorators: Option<Vec<DecoratorId>> =
1396 match block_builder.try_into_basic_block()? {
1397 BasicBlockOrDecorators::BasicBlock(basic_block_id) => {
1398 body_node_ids.push(basic_block_id);
1399 None
1400 },
1401 BasicBlockOrDecorators::Decorators(decorator_ids) => {
1402 Some(decorator_ids)
1404 },
1405 BasicBlockOrDecorators::Nothing => None,
1406 };
1407
1408 let procedure_body_id = if body_node_ids.is_empty() {
1409 if maybe_post_decorators.is_some() {
1413 return Err(Report::new(
1414 RelatedLabel::error("invalid procedure")
1415 .with_labeled_span(
1416 proc_ctx.span(),
1417 "body must contain at least one instruction if it has decorators",
1418 )
1419 .with_source_file(
1420 proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
1421 ),
1422 ));
1423 }
1424
1425 mast_forest_builder.ensure_block(
1426 vec![Operation::Noop],
1427 Vec::new(),
1428 vec![],
1429 vec![],
1430 vec![],
1431 vec![],
1432 )?
1433 } else {
1434 let asm_op = self.create_asmop_decorator(&proc_ctx.span(), "begin", proc_ctx);
1435 mast_forest_builder.join_nodes(body_node_ids, Some(asm_op))?
1436 };
1437
1438 if let Some(post_decorator_ids) = maybe_post_decorators {
1440 mast_forest_builder
1441 .append_after_exit(procedure_body_id, post_decorator_ids)
1442 .into_diagnostic()?;
1443 }
1444
1445 Ok(procedure_body_id)
1446 }
1447
1448 pub(super) fn resolve_target(
1455 &self,
1456 kind: InvokeKind,
1457 target: &InvocationTarget,
1458 caller_id: GlobalItemIndex,
1459 mast_forest_builder: &mut MastForestBuilder,
1460 ) -> Result<Option<ResolvedProcedure>, Report> {
1461 let caller = SymbolResolutionContext {
1462 span: target.span(),
1463 module: caller_id.module,
1464 kind: Some(kind),
1465 };
1466 let resolved = self.linker.resolve_invoke_target(&caller, target)?;
1467 match resolved {
1468 SymbolResolution::MastRoot(mast_root) => {
1469 let node = self.ensure_valid_procedure_mast_root(
1470 kind,
1471 target.span(),
1472 mast_root.into_inner(),
1473 mast_forest_builder,
1474 )?;
1475 Ok(Some(ResolvedProcedure { node, signature: None }))
1476 },
1477 SymbolResolution::Exact { gid, .. } => {
1478 match mast_forest_builder.get_procedure(gid) {
1479 Some(proc) => Ok(Some(ResolvedProcedure {
1480 node: proc.body_node_id(),
1481 signature: proc.signature(),
1482 })),
1483 None => match self.linker[gid].item() {
1486 SymbolItem::Compiled(ItemInfo::Procedure(p)) => {
1487 let node = self.ensure_valid_procedure_mast_root(
1488 kind,
1489 target.span(),
1490 p.digest,
1491 mast_forest_builder,
1492 )?;
1493 Ok(Some(ResolvedProcedure { node, signature: p.signature.clone() }))
1494 },
1495 SymbolItem::Procedure(_) => panic!(
1496 "AST procedure {gid:?} exists in the linker, but not in the MastForestBuilder"
1497 ),
1498 SymbolItem::Alias { .. } => {
1499 unreachable!("unexpected reference to ast alias item from {gid:?}")
1500 },
1501 SymbolItem::Compiled(_) | SymbolItem::Type(_) | SymbolItem::Constant(_) => {
1502 Ok(None)
1503 },
1504 },
1505 }
1506 },
1507 SymbolResolution::Module { .. }
1508 | SymbolResolution::External(_)
1509 | SymbolResolution::Local(_) => unreachable!(),
1510 }
1511 }
1512
1513 fn ensure_valid_procedure_mast_root(
1518 &self,
1519 kind: InvokeKind,
1520 span: SourceSpan,
1521 mast_root: Word,
1522 mast_forest_builder: &mut MastForestBuilder,
1523 ) -> Result<MastNodeId, Report> {
1524 let current_source_file = self.source_manager.get(span.source_id()).ok();
1526
1527 if matches!(kind, InvokeKind::SysCall) && self.linker.has_nonempty_kernel() {
1528 if !self.linker.kernel().contains_proc(mast_root) {
1532 let callee = mast_forest_builder
1533 .find_procedure_by_mast_root(&mast_root)
1534 .map(|proc| proc.path().clone())
1535 .unwrap_or_else(|| {
1536 let digest_path = format!("{mast_root}");
1537 Arc::<Path>::from(Path::new(&digest_path))
1538 });
1539 return Err(Report::new(LinkerError::InvalidSysCallTarget {
1540 span,
1541 source_file: current_source_file,
1542 callee,
1543 }));
1544 }
1545 }
1546
1547 mast_forest_builder.ensure_external_link(mast_root)
1548 }
1549}
1550
1551struct SubgraphRoot {
1559 proc_id: GlobalItemIndex,
1560 is_program_entrypoint: bool,
1561}
1562
1563impl SubgraphRoot {
1564 fn with_entrypoint(proc_id: GlobalItemIndex) -> Self {
1565 Self { proc_id, is_program_entrypoint: true }
1566 }
1567
1568 fn not_as_entrypoint(proc_id: GlobalItemIndex) -> Self {
1569 Self { proc_id, is_program_entrypoint: false }
1570 }
1571}
1572
1573pub(crate) struct BodyWrapper {
1576 pub prologue: Vec<Operation>,
1577 pub epilogue: Vec<Operation>,
1578}
1579
1580pub(super) struct ResolvedProcedure {
1581 pub node: MastNodeId,
1582 pub signature: Option<Arc<FunctionType>>,
1583}