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 item.source_library_commitment(),
670 item.source_root_id(),
671 mast_forest_builder,
672 )?;
673 ResolvedProcedure { node, signature: item.signature.clone() }
674 },
675 };
676 let digest = item.digest;
677 let ResolvedProcedure { node, signature } = resolved;
678 let attributes = item.attributes.clone();
679 let pctx = ProcedureContext::new(
680 gid,
681 false,
682 symbol_path.clone(),
683 Visibility::Public,
684 signature.clone(),
685 module_kind.is_kernel(),
686 self.source_manager.clone(),
687 );
688
689 let procedure = pctx.into_procedure(digest, node);
690 self.linker.register_procedure_root(gid, digest)?;
691 mast_forest_builder.insert_procedure(gid, procedure)?;
692 LibraryExport::Procedure(ProcedureExport {
693 node,
694 path: symbol_path,
695 signature: signature.map(|sig| (*sig).clone()),
696 attributes,
697 })
698 },
699 SymbolItem::Compiled(ItemInfo::Constant(item)) => {
700 LibraryExport::Constant(ConstantExport {
701 path: symbol_path,
702 value: item.value.clone(),
703 })
704 },
705 SymbolItem::Compiled(ItemInfo::Type(item)) => {
706 LibraryExport::Type(TypeExport { path: symbol_path, ty: item.ty.clone() })
707 },
708 SymbolItem::Procedure(_) => {
709 self.compile_subgraph(SubgraphRoot::not_as_entrypoint(gid), mast_forest_builder)?;
710 let node = mast_forest_builder
711 .get_procedure(gid)
712 .expect("compilation succeeded but root not found in cache")
713 .body_node_id();
714 let signature = self.linker.resolve_signature(gid)?;
715 let attributes = self.linker.resolve_attributes(gid)?;
716 LibraryExport::Procedure(ProcedureExport {
717 node,
718 path: symbol_path,
719 signature: signature.map(Arc::unwrap_or_clone),
720 attributes,
721 })
722 },
723 SymbolItem::Constant(item) => {
724 let value = self.linker.const_eval(gid, &item.value, &mut cache)?;
726
727 LibraryExport::Constant(ConstantExport { path: symbol_path, value })
728 },
729 SymbolItem::Type(item) => {
730 let ty = self.linker.resolve_type(item.span(), gid)?;
731 LibraryExport::Type(TypeExport { path: symbol_path, ty })
732 },
733
734 SymbolItem::Alias { alias, resolved } => {
735 if let Some(resolved) = resolved.get() {
736 return self.export_symbol(
737 resolved,
738 module_kind,
739 symbol_path,
740 mast_forest_builder,
741 );
742 }
743
744 let Some(ResolvedProcedure { node, signature }) = self.resolve_target(
745 InvokeKind::ProcRef,
746 &alias.target().into(),
747 gid,
748 mast_forest_builder,
749 )?
750 else {
751 return Err(self.unresolved_alias_report("export", &symbol_path, alias));
752 };
753
754 let digest = mast_forest_builder
755 .get_mast_node(node)
756 .expect("resolved alias export node must exist")
757 .digest();
758 let pctx = ProcedureContext::new(
759 gid,
760 false,
761 symbol_path.clone(),
762 Visibility::Public,
763 signature.clone(),
764 module_kind.is_kernel(),
765 self.source_manager.clone(),
766 );
767 let procedure = pctx.into_procedure(digest, node);
768 self.linker.register_procedure_root(gid, digest)?;
769 mast_forest_builder.insert_procedure(gid, procedure)?;
770
771 return Ok(LibraryExport::Procedure(ProcedureExport {
772 node,
773 path: symbol_path,
774 signature: signature.map(Arc::unwrap_or_clone),
775 attributes: Default::default(),
776 }));
777 },
778 };
779
780 Ok(export)
781 }
782
783 pub fn assemble_program(self, source: impl Parse) -> Result<Program, Report> {
791 let options = ParseOptions {
792 kind: ModuleKind::Executable,
793 warnings_as_errors: self.warnings_as_errors,
794 path: Some(Path::exec_path().into()),
795 };
796
797 let program = source.parse_with_options(self.source_manager.clone(), options)?;
798 assert!(program.is_executable());
799
800 self.assemble_executable_modules(program, [])?.into_program()
801 }
802
803 pub(crate) fn assemble_library_modules(
804 mut self,
805 modules: impl IntoIterator<Item = Box<ast::Module>>,
806 kind: TargetType,
807 ) -> Result<AssemblyProduct, Report> {
808 let module_indices = self.linker.link(modules)?;
809 self.assemble_library_product(&module_indices, kind)
810 }
811
812 pub(crate) fn assemble_kernel_module(
813 mut self,
814 module: Box<ast::Module>,
815 ) -> Result<AssemblyProduct, Report> {
816 let module_indices = self.linker.link_kernel(module)?;
817 self.assemble_library_product(&module_indices, TargetType::Kernel)
818 }
819
820 pub(crate) fn assemble_executable_modules(
821 mut self,
822 program: Box<ast::Module>,
823 support_modules: impl IntoIterator<Item = Box<ast::Module>>,
824 ) -> Result<AssemblyProduct, Report> {
825 self.linker.link_modules(support_modules)?;
826
827 let module_index = self.linker.link([program])?[0];
829
830 let entrypoint = self.linker[module_index]
833 .symbols()
834 .position(|symbol| symbol.name().as_str() == Ident::MAIN)
835 .map(|index| module_index + ItemIndex::new(index))
836 .ok_or(SemanticAnalysisError::MissingEntrypoint)?;
837
838 let staticlibs = self.linker.libraries().filter_map(|lib| {
840 if matches!(lib.linkage, Linkage::Static) {
841 Some(lib.mast.as_ref())
842 } else {
843 None
844 }
845 });
846 let mut mast_forest_builder = MastForestBuilder::new(staticlibs)?;
847 mast_forest_builder.set_emit_debug_info(self.emit_debug_info);
848
849 if let Some(advice_map) = self.linker[module_index].advice_map() {
850 mast_forest_builder.merge_advice_map(advice_map)?;
851 }
852
853 self.compile_subgraph(SubgraphRoot::with_entrypoint(entrypoint), &mut mast_forest_builder)?;
854 let entry_node_id = mast_forest_builder
855 .get_procedure(entrypoint)
856 .expect("compilation succeeded but root not found in cache")
857 .body_node_id();
858
859 let (mast_forest, id_remappings) = mast_forest_builder.build();
861 let entry_node_id = *id_remappings.get(&entry_node_id).unwrap_or(&entry_node_id);
862
863 self.finish_program_product(mast_forest, entry_node_id, self.linker.kernel().clone())
864 }
865
866 fn finish_library_product(
867 &self,
868 mut mast_forest: miden_core::mast::MastForest,
869 exports: BTreeMap<Arc<Path>, LibraryExport>,
870 kind: TargetType,
871 ) -> Result<AssemblyProduct, Report> {
872 self.apply_debug_options(&mut mast_forest);
873
874 let library = Library::new(Arc::new(mast_forest), exports)?;
875 let manifest = PackageManifest::from_library(&library);
876 let debug_info = self.emit_debug_info.then(|| {
877 #[cfg_attr(not(feature = "std"), expect(unused_mut))]
878 let mut debug_info = self.debug_info.clone();
879 #[cfg(feature = "std")]
880 if let Some(trimmer) = self.source_path_trimmer() {
881 debug_info.trim_paths(&trimmer);
882 }
883 debug_info
884 });
885
886 Ok(AssemblyProduct::new(kind, Arc::new(library), None, manifest, debug_info))
887 }
888
889 fn finish_program_product(
890 &self,
891 mut mast_forest: miden_core::mast::MastForest,
892 entrypoint: MastNodeId,
893 kernel: Kernel,
894 ) -> Result<AssemblyProduct, Report> {
895 self.apply_debug_options(&mut mast_forest);
896
897 let mast = Arc::new(mast_forest);
898 let entry: Arc<Path> = Path::exec_path().join(ast::ProcedureName::MAIN_PROC_NAME).into();
899 let entrypoint = LibraryExport::Procedure(ProcedureExport {
900 node: entrypoint,
901 path: entry.clone(),
902 signature: None,
903 attributes: Default::default(),
904 });
905 let library = Arc::new(Library::new(mast, BTreeMap::from_iter([(entry, entrypoint)]))?);
906 let manifest = PackageManifest::from_library(&library);
907 let debug_info = self.emit_debug_info.then(|| {
908 #[cfg_attr(not(feature = "std"), expect(unused_mut))]
909 let mut debug_info = self.debug_info.clone();
910 #[cfg(feature = "std")]
911 if let Some(trimmer) = self.source_path_trimmer() {
912 debug_info.trim_paths(&trimmer);
913 }
914 debug_info
915 });
916
917 Ok(AssemblyProduct::new(
918 TargetType::Executable,
919 library,
920 Some(kernel),
921 manifest,
922 debug_info,
923 ))
924 }
925
926 fn apply_debug_options(&self, mast_forest: &mut miden_core::mast::MastForest) {
927 if !self.emit_debug_info {
928 mast_forest.clear_debug_info();
929 return;
930 }
931
932 if self.trim_paths {
933 #[cfg(feature = "std")]
934 if let Some(trimmer) = self.source_path_trimmer() {
935 mast_forest.debug_info_mut().rewrite_source_locations(
936 |location| trimmer.trim_location(location),
937 |location| trimmer.trim_file_line_col(location),
938 );
939 }
940 }
941 }
942
943 #[cfg(feature = "std")]
944 fn source_path_trimmer(&self) -> Option<debuginfo::SourcePathTrimmer> {
945 if !self.trim_paths {
946 return None;
947 }
948
949 std::env::current_dir().ok().map(debuginfo::SourcePathTrimmer::new)
950 }
951
952 fn compile_subgraph(
957 &mut self,
958 root: SubgraphRoot,
959 mast_forest_builder: &mut MastForestBuilder,
960 ) -> Result<(), Report> {
961 let mut worklist: Vec<GlobalItemIndex> = self
962 .linker
963 .topological_sort_from_root(root.proc_id)
964 .map_err(|cycle| {
965 let iter = cycle.into_node_ids();
966 let mut nodes = Vec::with_capacity(iter.len());
967 for node in iter {
968 let module = self.linker[node.module].path();
969 let proc = self.linker[node].name();
970 nodes.push(format!("{}", module.join(proc)));
971 }
972 LinkerError::Cycle { nodes: nodes.into() }
973 })?
974 .into_iter()
975 .filter(|&gid| {
976 matches!(
977 self.linker[gid].item(),
978 SymbolItem::Procedure(_) | SymbolItem::Alias { .. }
979 )
980 })
981 .collect();
982
983 assert!(!worklist.is_empty());
984
985 self.process_graph_worklist(&mut worklist, &root, mast_forest_builder)
986 }
987
988 fn process_graph_worklist(
990 &mut self,
991 worklist: &mut Vec<GlobalItemIndex>,
992 root: &SubgraphRoot,
993 mast_forest_builder: &mut MastForestBuilder,
994 ) -> Result<(), Report> {
995 while let Some(procedure_gid) = worklist.pop() {
998 if let Some(proc) = mast_forest_builder.get_procedure(procedure_gid) {
1000 self.linker.register_procedure_root(procedure_gid, proc.mast_root())?;
1001 continue;
1002 }
1003 let (module_kind, module_path) = {
1005 let module = &self.linker[procedure_gid.module];
1006 (module.kind(), module.path().clone())
1007 };
1008 match self.linker[procedure_gid].item() {
1009 SymbolItem::Procedure(proc) => {
1010 let proc = proc.borrow();
1011 let num_locals = proc.num_locals();
1012 let path = Arc::<Path>::from(module_path.join(proc.name().as_str()));
1013 let signature = self.linker.resolve_signature(procedure_gid)?;
1014 let is_program_entrypoint =
1015 root.is_program_entrypoint && root.proc_id == procedure_gid;
1016
1017 let pctx = ProcedureContext::new(
1018 procedure_gid,
1019 is_program_entrypoint,
1020 path.clone(),
1021 proc.visibility(),
1022 signature.clone(),
1023 module_kind.is_kernel(),
1024 self.source_manager.clone(),
1025 )
1026 .with_num_locals(num_locals)
1027 .with_span(proc.span());
1028
1029 let procedure = self.compile_procedure(pctx, mast_forest_builder)?;
1031 self.debug_info
1039 .register_procedure_debug_info(&procedure, self.source_manager.as_ref())?;
1040
1041 drop(proc);
1043 self.linker.register_procedure_root(procedure_gid, procedure.mast_root())?;
1044 mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
1045 },
1046 SymbolItem::Alias { alias, resolved } => {
1047 let path: Arc<Path> = module_path.join(alias.name().as_str()).into();
1048 let procedure_gid = match resolved.get() {
1049 Some(procedure_gid) => {
1050 match self.linker[procedure_gid].item() {
1051 SymbolItem::Procedure(_)
1052 | SymbolItem::Compiled(ItemInfo::Procedure(_)) => {},
1053 SymbolItem::Constant(_)
1054 | SymbolItem::Type(_)
1055 | SymbolItem::Compiled(_) => {
1056 continue;
1057 },
1058 SymbolItem::Alias { .. } => unreachable!(),
1063 }
1064 procedure_gid
1065 },
1066 None => procedure_gid,
1067 };
1068 let is_program_entrypoint = false;
1070 let mut pctx = ProcedureContext::new(
1071 procedure_gid,
1072 is_program_entrypoint,
1073 path,
1074 Visibility::Public,
1075 None,
1076 module_kind.is_kernel(),
1077 self.source_manager.clone(),
1078 )
1079 .with_span(alias.span());
1080
1081 let Some(ResolvedProcedure { node: proc_node_id, signature }) = self
1085 .resolve_target(
1086 InvokeKind::ProcRef,
1087 &alias.target().into(),
1088 procedure_gid,
1089 mast_forest_builder,
1090 )?
1091 else {
1092 continue;
1093 };
1094
1095 pctx.set_signature(signature);
1096
1097 let proc_mast_root =
1098 mast_forest_builder.get_mast_node(proc_node_id).unwrap().digest();
1099
1100 let procedure = pctx.into_procedure(proc_mast_root, proc_node_id);
1101
1102 self.linker.register_procedure_root(procedure_gid, proc_mast_root)?;
1104 mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
1105 },
1106 SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
1107 },
1109 }
1110 }
1111
1112 Ok(())
1113 }
1114
1115 fn unresolved_alias_report(
1116 &self,
1117 action: &'static str,
1118 symbol_path: &Path,
1119 alias: &ast::Alias,
1120 ) -> Report {
1121 let span = alias.target().span();
1122 let reason = match alias.target() {
1123 ast::AliasTarget::MastRoot(_) => {
1124 "this digest target does not resolve to a known procedure"
1125 },
1126 ast::AliasTarget::Path(_) => "this alias target does not resolve to a concrete item",
1127 };
1128
1129 RelatedLabel::error(format!(
1130 "unable to {action} alias '{symbol_path}' targeting '{}'",
1131 alias.target()
1132 ))
1133 .with_labeled_span(span, reason)
1134 .with_help("aliases must resolve to a concrete item before they can be used")
1135 .with_source_file(self.source_manager.get(span.source_id()).ok())
1136 .into()
1137 }
1138
1139 fn compile_procedure(
1141 &self,
1142 mut proc_ctx: ProcedureContext,
1143 mast_forest_builder: &mut MastForestBuilder,
1144 ) -> Result<Procedure, Report> {
1145 let gid = proc_ctx.id();
1147
1148 let num_locals = proc_ctx.num_locals();
1149
1150 let proc = match self.linker[gid].item() {
1151 SymbolItem::Procedure(proc) => proc.borrow(),
1152 _ => panic!("expected item to be a procedure AST"),
1153 };
1154 let body_wrapper = if proc_ctx.is_program_entrypoint() {
1155 assert!(num_locals == 0, "program entrypoint cannot have locals");
1156
1157 Some(BodyWrapper {
1158 prologue: fmp_initialization_sequence(),
1159 epilogue: Vec::new(),
1160 })
1161 } else if num_locals > 0 {
1162 Some(BodyWrapper {
1163 prologue: fmp_start_frame_sequence(num_locals),
1164 epilogue: fmp_end_frame_sequence(num_locals),
1165 })
1166 } else {
1167 None
1168 };
1169
1170 let proc_body_id =
1171 self.compile_body(proc.iter(), &mut proc_ctx, body_wrapper, mast_forest_builder, 0)?;
1172
1173 let proc_body_node = mast_forest_builder
1174 .get_mast_node(proc_body_id)
1175 .expect("no MAST node for compiled procedure");
1176 Ok(proc_ctx.into_procedure(proc_body_node.digest(), proc_body_id))
1177 }
1178
1179 fn create_asmop_decorator(
1181 &self,
1182 span: &SourceSpan,
1183 op_name: &str,
1184 proc_ctx: &ProcedureContext,
1185 ) -> AssemblyOp {
1186 let location = proc_ctx.source_manager().location(*span).ok();
1187 let context_name = proc_ctx.path().to_string();
1188 let num_cycles = 0;
1189 AssemblyOp::new(location, context_name, num_cycles, op_name.to_string())
1190 }
1191
1192 fn compile_body<'a, I>(
1193 &self,
1194 body: I,
1195 proc_ctx: &mut ProcedureContext,
1196 wrapper: Option<BodyWrapper>,
1197 mast_forest_builder: &mut MastForestBuilder,
1198 nesting_depth: usize,
1199 ) -> Result<MastNodeId, Report>
1200 where
1201 I: Iterator<Item = &'a ast::Op>,
1202 {
1203 use ast::Op;
1204
1205 let mut body_node_ids: Vec<MastNodeId> = Vec::new();
1206 let mut block_builder = BasicBlockBuilder::new(wrapper, mast_forest_builder);
1207
1208 for op in body {
1209 match op {
1210 Op::Inst(inst) => {
1211 if let Some(node_id) =
1212 self.compile_instruction(inst, &mut block_builder, proc_ctx)?
1213 {
1214 if let Some(basic_block_id) = block_builder.make_basic_block()? {
1215 body_node_ids.push(basic_block_id);
1216 } else if let Some(decorator_ids) = block_builder.drain_decorators() {
1217 block_builder
1218 .mast_forest_builder_mut()
1219 .append_before_enter(node_id, decorator_ids)
1220 .into_diagnostic()?;
1221 }
1222
1223 body_node_ids.push(node_id);
1224 }
1225 },
1226
1227 Op::If { then_blk, else_blk, span } => {
1228 if let Some(basic_block_id) = block_builder.make_basic_block()? {
1229 body_node_ids.push(basic_block_id);
1230 }
1231
1232 let next_depth = nesting_depth + 1;
1233 if next_depth > MAX_CONTROL_FLOW_NESTING {
1234 return Err(Report::new(AssemblerError::ControlFlowNestingDepthExceeded {
1235 span: *span,
1236 source_file: proc_ctx.source_manager().get(span.source_id()).ok(),
1237 max_depth: MAX_CONTROL_FLOW_NESTING,
1238 }));
1239 }
1240
1241 let then_blk = self.compile_body(
1242 then_blk.iter(),
1243 proc_ctx,
1244 None,
1245 block_builder.mast_forest_builder_mut(),
1246 next_depth,
1247 )?;
1248 let else_blk = self.compile_body(
1249 else_blk.iter(),
1250 proc_ctx,
1251 None,
1252 block_builder.mast_forest_builder_mut(),
1253 next_depth,
1254 )?;
1255
1256 let asm_op = self.create_asmop_decorator(span, "if.true", proc_ctx);
1257 let mut split_builder = SplitNodeBuilder::new([then_blk, else_blk]);
1258 if let Some(decorator_ids) = block_builder.drain_decorators() {
1259 split_builder.append_before_enter(decorator_ids);
1260 }
1261
1262 let split_node_id = block_builder
1263 .mast_forest_builder_mut()
1264 .ensure_node_with_asm_op(split_builder, asm_op)?;
1265
1266 body_node_ids.push(split_node_id);
1267 },
1268
1269 Op::Repeat { count, body, span } => {
1270 if let Some(basic_block_id) = block_builder.make_basic_block()? {
1271 body_node_ids.push(basic_block_id);
1272 }
1273
1274 let next_depth = nesting_depth + 1;
1275 if next_depth > MAX_CONTROL_FLOW_NESTING {
1276 return Err(Report::new(AssemblerError::ControlFlowNestingDepthExceeded {
1277 span: *span,
1278 source_file: proc_ctx.source_manager().get(span.source_id()).ok(),
1279 max_depth: MAX_CONTROL_FLOW_NESTING,
1280 }));
1281 }
1282
1283 let repeat_node_id = self.compile_body(
1284 body.iter(),
1285 proc_ctx,
1286 None,
1287 block_builder.mast_forest_builder_mut(),
1288 next_depth,
1289 )?;
1290
1291 let iteration_count = (*count).expect_value();
1292 if iteration_count == 0 {
1293 return Err(RelatedLabel::error("invalid repeat count")
1294 .with_help("repeat count must be greater than 0")
1295 .with_labeled_span(count.span(), "repeat count must be at least 1")
1296 .with_source_file(
1297 proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
1298 )
1299 .into());
1300 }
1301 if iteration_count > MAX_REPEAT_COUNT {
1302 return Err(RelatedLabel::error("invalid repeat count")
1303 .with_help(format!(
1304 "repeat count must be less than or equal to {MAX_REPEAT_COUNT}",
1305 ))
1306 .with_labeled_span(
1307 count.span(),
1308 format!("repeat count exceeds {MAX_REPEAT_COUNT}"),
1309 )
1310 .with_source_file(
1311 proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
1312 )
1313 .into());
1314 }
1315
1316 if let Some(decorator_ids) = block_builder.drain_decorators() {
1317 let first_repeat_builder = block_builder.mast_forest_builder()
1322 [repeat_node_id]
1323 .clone()
1324 .to_builder(block_builder.mast_forest_builder().mast_forest())
1325 .with_before_enter(decorator_ids);
1326 let first_repeat_node_id = block_builder
1327 .mast_forest_builder_mut()
1328 .ensure_node_preserving_debug_vars(
1329 first_repeat_builder,
1330 repeat_node_id,
1331 )?;
1332
1333 body_node_ids.push(first_repeat_node_id);
1334 let remaining_iterations =
1335 iteration_count.checked_sub(1).ok_or_else(|| {
1336 Report::new(
1337 RelatedLabel::error("invalid repeat count")
1338 .with_help("repeat count must be greater than 0")
1339 .with_labeled_span(
1340 count.span(),
1341 "repeat count must be at least 1",
1342 )
1343 .with_source_file(
1344 proc_ctx
1345 .source_manager()
1346 .get(proc_ctx.span().source_id())
1347 .ok(),
1348 ),
1349 )
1350 })?;
1351 for _ in 0..remaining_iterations {
1352 body_node_ids.push(repeat_node_id);
1353 }
1354 } else {
1355 for _ in 0..iteration_count {
1356 body_node_ids.push(repeat_node_id);
1357 }
1358 }
1359 },
1360
1361 Op::While { body, span } => {
1362 if let Some(basic_block_id) = block_builder.make_basic_block()? {
1363 body_node_ids.push(basic_block_id);
1364 }
1365
1366 let next_depth = nesting_depth + 1;
1367 if next_depth > MAX_CONTROL_FLOW_NESTING {
1368 return Err(Report::new(AssemblerError::ControlFlowNestingDepthExceeded {
1369 span: *span,
1370 source_file: proc_ctx.source_manager().get(span.source_id()).ok(),
1371 max_depth: MAX_CONTROL_FLOW_NESTING,
1372 }));
1373 }
1374
1375 let loop_body_node_id = self.compile_body(
1376 body.iter(),
1377 proc_ctx,
1378 None,
1379 block_builder.mast_forest_builder_mut(),
1380 next_depth,
1381 )?;
1382 let mut loop_builder = LoopNodeBuilder::new(loop_body_node_id);
1383 if let Some(decorator_ids) = block_builder.drain_decorators() {
1384 loop_builder.append_before_enter(decorator_ids);
1385 }
1386
1387 let asm_op = self.create_asmop_decorator(span, "while.true", proc_ctx);
1388 let loop_node_id = block_builder
1389 .mast_forest_builder_mut()
1390 .ensure_node_with_asm_op(loop_builder, asm_op)?;
1391
1392 body_node_ids.push(loop_node_id);
1393 },
1394 }
1395 }
1396
1397 let maybe_post_decorators: Option<Vec<DecoratorId>> =
1398 match block_builder.try_into_basic_block()? {
1399 BasicBlockOrDecorators::BasicBlock(basic_block_id) => {
1400 body_node_ids.push(basic_block_id);
1401 None
1402 },
1403 BasicBlockOrDecorators::Decorators(decorator_ids) => {
1404 Some(decorator_ids)
1406 },
1407 BasicBlockOrDecorators::Nothing => None,
1408 };
1409
1410 let procedure_body_id = if body_node_ids.is_empty() {
1411 if maybe_post_decorators.is_some() {
1415 return Err(Report::new(
1416 RelatedLabel::error("invalid procedure")
1417 .with_labeled_span(
1418 proc_ctx.span(),
1419 "body must contain at least one instruction if it has decorators",
1420 )
1421 .with_source_file(
1422 proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
1423 ),
1424 ));
1425 }
1426
1427 mast_forest_builder.ensure_block(
1428 vec![Operation::Noop],
1429 Vec::new(),
1430 vec![],
1431 vec![],
1432 vec![],
1433 vec![],
1434 )?
1435 } else {
1436 let asm_op = self.create_asmop_decorator(&proc_ctx.span(), "begin", proc_ctx);
1437 mast_forest_builder.join_nodes(body_node_ids, Some(asm_op))?
1438 };
1439
1440 if let Some(post_decorator_ids) = maybe_post_decorators {
1442 mast_forest_builder
1443 .append_after_exit(procedure_body_id, post_decorator_ids)
1444 .into_diagnostic()?;
1445 }
1446
1447 Ok(procedure_body_id)
1448 }
1449
1450 pub(super) fn resolve_target(
1457 &self,
1458 kind: InvokeKind,
1459 target: &InvocationTarget,
1460 caller_id: GlobalItemIndex,
1461 mast_forest_builder: &mut MastForestBuilder,
1462 ) -> Result<Option<ResolvedProcedure>, Report> {
1463 let caller = SymbolResolutionContext {
1464 span: target.span(),
1465 module: caller_id.module,
1466 kind: Some(kind),
1467 };
1468 let resolved = self.linker.resolve_invoke_target(&caller, target)?;
1469 match resolved {
1470 SymbolResolution::MastRoot(mast_root) => {
1471 let node = self.ensure_valid_procedure_mast_root(
1476 kind,
1477 target.span(),
1478 mast_root.into_inner(),
1479 None,
1480 None,
1481 mast_forest_builder,
1482 )?;
1483 Ok(Some(ResolvedProcedure { node, signature: None }))
1484 },
1485 SymbolResolution::Exact { gid, .. } => {
1486 match mast_forest_builder.get_procedure(gid) {
1487 Some(proc) => Ok(Some(ResolvedProcedure {
1488 node: proc.body_node_id(),
1489 signature: proc.signature(),
1490 })),
1491 None => match self.linker[gid].item() {
1494 SymbolItem::Compiled(ItemInfo::Procedure(p)) => {
1495 let node = self.ensure_valid_procedure_mast_root(
1496 kind,
1497 target.span(),
1498 p.digest,
1499 p.source_library_commitment(),
1500 p.source_root_id(),
1501 mast_forest_builder,
1502 )?;
1503 Ok(Some(ResolvedProcedure { node, signature: p.signature.clone() }))
1504 },
1505 SymbolItem::Procedure(_) => panic!(
1506 "AST procedure {gid:?} exists in the linker, but not in the MastForestBuilder"
1507 ),
1508 SymbolItem::Alias { .. } => {
1509 unreachable!("unexpected reference to ast alias item from {gid:?}")
1510 },
1511 SymbolItem::Compiled(_) | SymbolItem::Type(_) | SymbolItem::Constant(_) => {
1512 Ok(None)
1513 },
1514 },
1515 }
1516 },
1517 SymbolResolution::Module { .. }
1518 | SymbolResolution::External(_)
1519 | SymbolResolution::Local(_) => unreachable!(),
1520 }
1521 }
1522
1523 fn ensure_valid_procedure_mast_root(
1528 &self,
1529 kind: InvokeKind,
1530 span: SourceSpan,
1531 mast_root: Word,
1532 source_library_commitment: Option<Word>,
1533 source_root_id: Option<MastNodeId>,
1534 mast_forest_builder: &mut MastForestBuilder,
1535 ) -> Result<MastNodeId, Report> {
1536 let current_source_file = self.source_manager.get(span.source_id()).ok();
1538
1539 if matches!(kind, InvokeKind::SysCall) && self.linker.has_nonempty_kernel() {
1540 if !self.linker.kernel().contains_proc(mast_root) {
1544 let callee = mast_forest_builder
1545 .find_procedure_by_mast_root(&mast_root)
1546 .map(|proc| proc.path().clone())
1547 .unwrap_or_else(|| {
1548 let digest_path = format!("{mast_root}");
1549 Arc::<Path>::from(Path::new(&digest_path))
1550 });
1551 return Err(Report::new(LinkerError::InvalidSysCallTarget {
1552 span,
1553 source_file: current_source_file,
1554 callee,
1555 }));
1556 }
1557 }
1558
1559 mast_forest_builder.ensure_external_link_with_source(
1560 mast_root,
1561 source_library_commitment,
1562 source_root_id,
1563 )
1564 }
1565}
1566
1567struct SubgraphRoot {
1575 proc_id: GlobalItemIndex,
1576 is_program_entrypoint: bool,
1577}
1578
1579impl SubgraphRoot {
1580 fn with_entrypoint(proc_id: GlobalItemIndex) -> Self {
1581 Self { proc_id, is_program_entrypoint: true }
1582 }
1583
1584 fn not_as_entrypoint(proc_id: GlobalItemIndex) -> Self {
1585 Self { proc_id, is_program_entrypoint: false }
1586 }
1587}
1588
1589pub(crate) struct BodyWrapper {
1592 pub prologue: Vec<Operation>,
1593 pub epilogue: Vec<Operation>,
1594}
1595
1596pub(super) struct ResolvedProcedure {
1597 pub node: MastNodeId,
1598 pub signature: Option<Arc<FunctionType>>,
1599}