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
173impl Assembler {
176 #[inline]
180 pub fn compile_and_statically_link(&mut self, module: impl Parse) -> Result<&mut Self, Report> {
181 self.compile_and_statically_link_all([module])
182 }
183
184 pub fn compile_and_statically_link_all(
189 &mut self,
190 modules: impl IntoIterator<Item = impl Parse>,
191 ) -> Result<&mut Self, Report> {
192 let modules = modules
193 .into_iter()
194 .map(|module| {
195 module.parse_with_options(
196 self.source_manager.clone(),
197 ParseOptions {
198 warnings_as_errors: self.warnings_as_errors,
199 ..ParseOptions::for_library()
200 },
201 )
202 })
203 .collect::<Result<Vec<_>, Report>>()?;
204
205 self.linker.link_modules(modules)?;
206
207 Ok(self)
208 }
209
210 #[cfg(feature = "std")]
242 pub fn compile_and_statically_link_from_dir(
243 &mut self,
244 dir: impl AsRef<std::path::Path>,
245 namespace: impl AsRef<Path>,
246 ) -> Result<(), Report> {
247 use miden_assembly_syntax::parser;
248
249 let namespace = namespace.as_ref();
250 let modules = parser::read_modules_from_dir(
251 dir,
252 namespace,
253 self.source_manager.clone(),
254 self.warnings_as_errors,
255 )?;
256 self.linker.link_modules(modules)?;
257 Ok(())
258 }
259
260 pub fn link_library(
274 &mut self,
275 library: impl AsRef<Library>,
276 linkage: Linkage,
277 ) -> Result<(), Report> {
278 self.linker
279 .link_library(LinkLibrary::from_library(library.as_ref()).with_linkage(linkage))
280 .map_err(Report::from)
281 }
282
283 pub fn link_dynamic_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> {
309 let library = LinkLibrary::from_library(library.as_ref()).with_linkage(Linkage::Dynamic);
310 self.linker.link_library(library).map_err(Report::from)
311 }
312
313 pub fn with_dynamic_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
317 self.link_dynamic_library(library)?;
318 Ok(self)
319 }
320
321 pub fn link_static_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> {
330 let library = LinkLibrary::from_library(library.as_ref()).with_linkage(Linkage::Static);
331 self.linker.link_library(library).map_err(Report::from)
332 }
333
334 pub fn with_static_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
338 self.link_static_library(library)?;
339 Ok(self)
340 }
341
342 pub fn link_package(
344 &mut self,
345 package: Arc<miden_mast_package::Package>,
346 linkage: Linkage,
347 ) -> Result<(), Report> {
348 match package.kind {
349 TargetType::Kernel => {
350 if !self.kernel().is_empty() {
351 return Err(Report::msg(format!(
352 "duplicate kernels present in the dependency graph: '{}@{
353 }' conflicts with another kernel we've already linked",
354 &package.name, &package.version
355 )));
356 }
357
358 let kernel_module = package.kernel_module_info()?;
359 let kernel = package.to_kernel()?;
360 self.linker.link_with_kernel(kernel, kernel_module)?;
361 Ok(())
362 },
363 TargetType::Executable => {
364 Err(Report::msg("cannot add executable packages to an assembler"))
365 },
366 _ => {
367 self.linker
368 .link_library(LinkLibrary::from_package(package).with_linkage(linkage))?;
369 Ok(())
370 },
371 }
372 }
373}
374
375impl Assembler {
378 pub fn warnings_as_errors(&self) -> bool {
380 self.warnings_as_errors
381 }
382
383 pub fn kernel(&self) -> &Kernel {
387 self.linker.kernel()
388 }
389
390 #[cfg(any(feature = "std", all(test, feature = "std")))]
391 pub(crate) fn source_manager(&self) -> Arc<dyn SourceManager> {
392 self.source_manager.clone()
393 }
394
395 #[cfg(any(test, feature = "testing"))]
396 #[doc(hidden)]
397 pub fn linker(&self) -> &Linker {
398 &self.linker
399 }
400}
401
402impl Assembler {
405 pub fn assemble_library(
411 self,
412 modules: impl IntoIterator<Item = impl Parse>,
413 ) -> Result<Arc<Library>, Report> {
414 let modules = modules
415 .into_iter()
416 .map(|module| {
417 module.parse_with_options(
418 self.source_manager.clone(),
419 ParseOptions {
420 warnings_as_errors: self.warnings_as_errors,
421 ..ParseOptions::for_library()
422 },
423 )
424 })
425 .collect::<Result<Vec<_>, Report>>()?;
426
427 Ok(self.assemble_library_modules(modules, TargetType::Library)?.into_artifact())
428 }
429
430 #[cfg(feature = "std")]
465 pub fn assemble_library_from_dir(
466 self,
467 dir: impl AsRef<std::path::Path>,
468 namespace: impl AsRef<Path>,
469 ) -> Result<Arc<Library>, Report> {
470 use miden_assembly_syntax::parser;
471
472 let dir = dir.as_ref();
473 let namespace = namespace.as_ref();
474
475 let source_manager = self.source_manager.clone();
476 let modules =
477 parser::read_modules_from_dir(dir, namespace, source_manager, self.warnings_as_errors)?;
478 self.assemble_library(modules)
479 }
480
481 pub fn assemble_kernel(self, module: impl Parse) -> Result<KernelLibrary, Report> {
487 let module = module.parse_with_options(
488 self.source_manager.clone(),
489 ParseOptions {
490 path: Some(Path::kernel_path().into()),
491 warnings_as_errors: self.warnings_as_errors,
492 ..ParseOptions::for_kernel()
493 },
494 )?;
495
496 self.assemble_kernel_module(module)?.into_kernel_library()
497 }
498
499 #[cfg(feature = "std")]
513 pub fn assemble_kernel_from_dir(
514 mut self,
515 sys_module_path: impl AsRef<std::path::Path>,
516 lib_dir: Option<impl AsRef<std::path::Path>>,
517 ) -> Result<KernelLibrary, Report> {
518 if let Some(lib_dir) = lib_dir {
520 self.compile_and_statically_link_from_dir(lib_dir, Path::kernel_path())?;
521 }
522
523 self.assemble_kernel(sys_module_path.as_ref())
524 }
525
526 fn assemble_library_product(
528 mut self,
529 module_indices: &[ModuleIndex],
530 kind: TargetType,
531 ) -> Result<AssemblyProduct, Report> {
532 let staticlibs = self.linker.libraries().filter_map(|lib| {
533 if matches!(lib.linkage, Linkage::Static) {
534 Some(lib.mast.as_ref())
535 } else {
536 None
537 }
538 });
539 let mut mast_forest_builder = MastForestBuilder::new(staticlibs)?;
540 mast_forest_builder.set_emit_debug_info(self.emit_debug_info);
541 let mut exports = {
542 let mut exports = BTreeMap::new();
543
544 for module_idx in module_indices.iter().copied() {
545 let module = &self.linker[module_idx];
546
547 if let Some(advice_map) = module.advice_map() {
548 mast_forest_builder.merge_advice_map(advice_map)?;
549 }
550
551 let module_kind = module.kind();
552 let module_path = module.path().clone();
553 for index in 0..module.symbols().len() {
554 let index = ItemIndex::new(index);
555 let gid = module_idx + index;
556
557 let path: Arc<Path> = {
558 let symbol = &self.linker[gid];
559 if !symbol.visibility().is_public() {
560 continue;
561 }
562 module_path.join(symbol.name()).into()
563 };
564 let export = self.export_symbol(
565 gid,
566 module_kind,
567 path.clone(),
568 &mut mast_forest_builder,
569 )?;
570 exports.insert(path, export);
571 }
572 }
573
574 exports
575 };
576
577 let (mast_forest, id_remappings) = mast_forest_builder.build();
578 for (_proc_name, export) in exports.iter_mut() {
579 match export {
580 LibraryExport::Procedure(export) => {
581 if let Some(&new_node_id) = id_remappings.get(&export.node) {
582 export.node = new_node_id;
583 }
584 },
585 LibraryExport::Constant(_) | LibraryExport::Type(_) => (),
586 }
587 }
588
589 self.finish_library_product(mast_forest, exports, kind)
590 }
591
592 fn export_symbol(
598 &mut self,
599 gid: GlobalItemIndex,
600 module_kind: ModuleKind,
601 symbol_path: Arc<Path>,
602 mast_forest_builder: &mut MastForestBuilder,
603 ) -> Result<LibraryExport, Report> {
604 log::trace!(target: "assembler::export_symbol", "exporting {} {symbol_path}", match self.linker[gid].item() {
605 SymbolItem::Compiled(ItemInfo::Procedure(_)) => "compiled procedure",
606 SymbolItem::Compiled(ItemInfo::Constant(_)) => "compiled constant",
607 SymbolItem::Compiled(ItemInfo::Type(_)) => "compiled type",
608 SymbolItem::Procedure(_) => "procedure",
609 SymbolItem::Constant(_) => "constant",
610 SymbolItem::Type(_) => "type",
611 SymbolItem::Alias { .. } => "alias",
612 });
613 let mut cache = crate::linker::ResolverCache::default();
614 let export = match self.linker[gid].item() {
615 SymbolItem::Compiled(ItemInfo::Procedure(item)) => {
616 let resolved = match mast_forest_builder.get_procedure(gid) {
617 Some(proc) => ResolvedProcedure {
618 node: proc.body_node_id(),
619 signature: proc.signature(),
620 },
621 None => {
624 let node = self.ensure_valid_procedure_mast_root(
625 InvokeKind::ProcRef,
626 SourceSpan::UNKNOWN,
627 item.digest,
628 mast_forest_builder,
629 )?;
630 ResolvedProcedure { node, signature: item.signature.clone() }
631 },
632 };
633 let digest = item.digest;
634 let ResolvedProcedure { node, signature } = resolved;
635 let attributes = item.attributes.clone();
636 let pctx = ProcedureContext::new(
637 gid,
638 false,
639 symbol_path.clone(),
640 Visibility::Public,
641 signature.clone(),
642 module_kind.is_kernel(),
643 self.source_manager.clone(),
644 );
645
646 let procedure = pctx.into_procedure(digest, node);
647 self.linker.register_procedure_root(gid, digest)?;
648 mast_forest_builder.insert_procedure(gid, procedure)?;
649 LibraryExport::Procedure(ProcedureExport {
650 node,
651 path: symbol_path,
652 signature: signature.map(|sig| (*sig).clone()),
653 attributes,
654 })
655 },
656 SymbolItem::Compiled(ItemInfo::Constant(item)) => {
657 LibraryExport::Constant(ConstantExport {
658 path: symbol_path,
659 value: item.value.clone(),
660 })
661 },
662 SymbolItem::Compiled(ItemInfo::Type(item)) => {
663 LibraryExport::Type(TypeExport { path: symbol_path, ty: item.ty.clone() })
664 },
665 SymbolItem::Procedure(_) => {
666 self.compile_subgraph(SubgraphRoot::not_as_entrypoint(gid), mast_forest_builder)?;
667 let node = mast_forest_builder
668 .get_procedure(gid)
669 .expect("compilation succeeded but root not found in cache")
670 .body_node_id();
671 let signature = self.linker.resolve_signature(gid)?;
672 let attributes = self.linker.resolve_attributes(gid)?;
673 LibraryExport::Procedure(ProcedureExport {
674 node,
675 path: symbol_path,
676 signature: signature.map(Arc::unwrap_or_clone),
677 attributes,
678 })
679 },
680 SymbolItem::Constant(item) => {
681 let value = self.linker.const_eval(gid, &item.value, &mut cache)?;
683
684 LibraryExport::Constant(ConstantExport { path: symbol_path, value })
685 },
686 SymbolItem::Type(item) => {
687 let ty = self.linker.resolve_type(item.span(), gid)?;
688 LibraryExport::Type(TypeExport { path: symbol_path, ty })
689 },
690
691 SymbolItem::Alias { alias, resolved } => {
692 let resolved = resolved.get().unwrap_or_else(|| {
694 panic!("unresolved alias {symbol_path} targeting: {}", alias.target())
695 });
696 return self.export_symbol(resolved, module_kind, symbol_path, mast_forest_builder);
697 },
698 };
699
700 Ok(export)
701 }
702
703 pub fn assemble_program(self, source: impl Parse) -> Result<Program, Report> {
711 let options = ParseOptions {
712 kind: ModuleKind::Executable,
713 warnings_as_errors: self.warnings_as_errors,
714 path: Some(Path::exec_path().into()),
715 };
716
717 let program = source.parse_with_options(self.source_manager.clone(), options)?;
718 assert!(program.is_executable());
719
720 self.assemble_executable_modules(program, [])?.into_program()
721 }
722
723 pub(crate) fn assemble_library_modules(
724 mut self,
725 modules: impl IntoIterator<Item = Box<ast::Module>>,
726 kind: TargetType,
727 ) -> Result<AssemblyProduct, Report> {
728 let module_indices = self.linker.link(modules)?;
729 self.assemble_library_product(&module_indices, kind)
730 }
731
732 pub(crate) fn assemble_kernel_module(
733 mut self,
734 module: Box<ast::Module>,
735 ) -> Result<AssemblyProduct, Report> {
736 let module_indices = self.linker.link_kernel(module)?;
737 self.assemble_library_product(&module_indices, TargetType::Kernel)
738 }
739
740 pub(crate) fn assemble_executable_modules(
741 mut self,
742 program: Box<ast::Module>,
743 support_modules: impl IntoIterator<Item = Box<ast::Module>>,
744 ) -> Result<AssemblyProduct, Report> {
745 self.linker.link_modules(support_modules)?;
746
747 let module_index = self.linker.link([program])?[0];
749
750 let entrypoint = self.linker[module_index]
753 .symbols()
754 .position(|symbol| symbol.name().as_str() == Ident::MAIN)
755 .map(|index| module_index + ItemIndex::new(index))
756 .ok_or(SemanticAnalysisError::MissingEntrypoint)?;
757
758 let staticlibs = self.linker.libraries().filter_map(|lib| {
760 if matches!(lib.linkage, Linkage::Static) {
761 Some(lib.mast.as_ref())
762 } else {
763 None
764 }
765 });
766 let mut mast_forest_builder = MastForestBuilder::new(staticlibs)?;
767 mast_forest_builder.set_emit_debug_info(self.emit_debug_info);
768
769 if let Some(advice_map) = self.linker[module_index].advice_map() {
770 mast_forest_builder.merge_advice_map(advice_map)?;
771 }
772
773 self.compile_subgraph(SubgraphRoot::with_entrypoint(entrypoint), &mut mast_forest_builder)?;
774 let entry_node_id = mast_forest_builder
775 .get_procedure(entrypoint)
776 .expect("compilation succeeded but root not found in cache")
777 .body_node_id();
778
779 let (mast_forest, id_remappings) = mast_forest_builder.build();
781 let entry_node_id = *id_remappings.get(&entry_node_id).unwrap_or(&entry_node_id);
782
783 self.finish_program_product(mast_forest, entry_node_id, self.linker.kernel().clone())
784 }
785
786 fn finish_library_product(
787 &self,
788 mut mast_forest: miden_core::mast::MastForest,
789 exports: BTreeMap<Arc<Path>, LibraryExport>,
790 kind: TargetType,
791 ) -> Result<AssemblyProduct, Report> {
792 self.apply_debug_options(&mut mast_forest);
793
794 let library = Library::new(Arc::new(mast_forest), exports)?;
795 let manifest = PackageManifest::from_library(&library);
796 let debug_info = self.emit_debug_info.then(|| {
797 #[cfg_attr(not(feature = "std"), expect(unused_mut))]
798 let mut debug_info = self.debug_info.clone();
799 #[cfg(feature = "std")]
800 if let Some(trimmer) = self.source_path_trimmer() {
801 debug_info.trim_paths(&trimmer);
802 }
803 debug_info
804 });
805
806 Ok(AssemblyProduct::new(kind, Arc::new(library), None, manifest, debug_info))
807 }
808
809 fn finish_program_product(
810 &self,
811 mut mast_forest: miden_core::mast::MastForest,
812 entrypoint: MastNodeId,
813 kernel: Kernel,
814 ) -> Result<AssemblyProduct, Report> {
815 self.apply_debug_options(&mut mast_forest);
816
817 let mast = Arc::new(mast_forest);
818 let entry: Arc<Path> =
819 ast::Path::exec_path().join(ast::ProcedureName::MAIN_PROC_NAME).into();
820 let entrypoint = LibraryExport::Procedure(ProcedureExport {
821 node: entrypoint,
822 path: entry.clone(),
823 signature: None,
824 attributes: Default::default(),
825 });
826 let library = Arc::new(Library::new(mast, BTreeMap::from_iter([(entry, entrypoint)]))?);
827 let manifest = PackageManifest::from_library(&library);
828 let debug_info = self.emit_debug_info.then(|| {
829 #[cfg_attr(not(feature = "std"), expect(unused_mut))]
830 let mut debug_info = self.debug_info.clone();
831 #[cfg(feature = "std")]
832 if let Some(trimmer) = self.source_path_trimmer() {
833 debug_info.trim_paths(&trimmer);
834 }
835 debug_info
836 });
837
838 Ok(AssemblyProduct::new(
839 TargetType::Executable,
840 library,
841 Some(kernel),
842 manifest,
843 debug_info,
844 ))
845 }
846
847 fn apply_debug_options(&self, mast_forest: &mut miden_core::mast::MastForest) {
848 if !self.emit_debug_info {
849 mast_forest.clear_debug_info();
850 return;
851 }
852
853 if self.trim_paths {
854 #[cfg(feature = "std")]
855 if let Some(trimmer) = self.source_path_trimmer() {
856 mast_forest.debug_info_mut().rewrite_source_locations(
857 |location| trimmer.trim_location(location),
858 |location| trimmer.trim_file_line_col(location),
859 );
860 }
861 }
862 }
863
864 #[cfg(feature = "std")]
865 fn source_path_trimmer(&self) -> Option<debuginfo::SourcePathTrimmer> {
866 if !self.trim_paths {
867 return None;
868 }
869
870 std::env::current_dir().ok().map(debuginfo::SourcePathTrimmer::new)
871 }
872
873 fn compile_subgraph(
878 &mut self,
879 root: SubgraphRoot,
880 mast_forest_builder: &mut MastForestBuilder,
881 ) -> Result<(), Report> {
882 let mut worklist: Vec<GlobalItemIndex> = self
883 .linker
884 .topological_sort_from_root(root.proc_id)
885 .map_err(|cycle| {
886 let iter = cycle.into_node_ids();
887 let mut nodes = Vec::with_capacity(iter.len());
888 for node in iter {
889 let module = self.linker[node.module].path();
890 let proc = self.linker[node].name();
891 nodes.push(format!("{}", module.join(proc)));
892 }
893 LinkerError::Cycle { nodes: nodes.into() }
894 })?
895 .into_iter()
896 .filter(|&gid| {
897 matches!(
898 self.linker[gid].item(),
899 SymbolItem::Procedure(_) | SymbolItem::Alias { .. }
900 )
901 })
902 .collect();
903
904 assert!(!worklist.is_empty());
905
906 self.process_graph_worklist(&mut worklist, &root, mast_forest_builder)
907 }
908
909 fn process_graph_worklist(
911 &mut self,
912 worklist: &mut Vec<GlobalItemIndex>,
913 root: &SubgraphRoot,
914 mast_forest_builder: &mut MastForestBuilder,
915 ) -> Result<(), Report> {
916 while let Some(procedure_gid) = worklist.pop() {
919 if let Some(proc) = mast_forest_builder.get_procedure(procedure_gid) {
921 self.linker.register_procedure_root(procedure_gid, proc.mast_root())?;
922 continue;
923 }
924 let (module_kind, module_path) = {
926 let module = &self.linker[procedure_gid.module];
927 (module.kind(), module.path().clone())
928 };
929 match self.linker[procedure_gid].item() {
930 SymbolItem::Procedure(proc) => {
931 let proc = proc.borrow();
932 let num_locals = proc.num_locals();
933 let path = Arc::<Path>::from(module_path.join(proc.name().as_str()));
934 let signature = self.linker.resolve_signature(procedure_gid)?;
935 let is_program_entrypoint =
936 root.is_program_entrypoint && root.proc_id == procedure_gid;
937
938 let pctx = ProcedureContext::new(
939 procedure_gid,
940 is_program_entrypoint,
941 path.clone(),
942 proc.visibility(),
943 signature.clone(),
944 module_kind.is_kernel(),
945 self.source_manager.clone(),
946 )
947 .with_num_locals(num_locals)
948 .with_span(proc.span());
949
950 let procedure = self.compile_procedure(pctx, mast_forest_builder)?;
952 self.debug_info
960 .register_procedure_debug_info(&procedure, self.source_manager.as_ref())?;
961
962 drop(proc);
964 self.linker.register_procedure_root(procedure_gid, procedure.mast_root())?;
965 mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
966 },
967 SymbolItem::Alias { alias, resolved } => {
968 let procedure_gid = resolved.get().expect("resolved alias");
969 match self.linker[procedure_gid].item() {
970 SymbolItem::Procedure(_) | SymbolItem::Compiled(ItemInfo::Procedure(_)) => {
971 },
972 SymbolItem::Constant(_) | SymbolItem::Type(_) | SymbolItem::Compiled(_) => {
973 continue;
974 },
975 SymbolItem::Alias { .. } => unreachable!(),
979 }
980 let path = module_path.join(alias.name().as_str()).into();
981 let is_program_entrypoint = false;
983 let mut pctx = ProcedureContext::new(
984 procedure_gid,
985 is_program_entrypoint,
986 path,
987 ast::Visibility::Public,
988 None,
989 module_kind.is_kernel(),
990 self.source_manager.clone(),
991 )
992 .with_span(alias.span());
993
994 let Some(ResolvedProcedure { node: proc_node_id, signature }) = self
998 .resolve_target(
999 InvokeKind::ProcRef,
1000 &alias.target().into(),
1001 procedure_gid,
1002 mast_forest_builder,
1003 )?
1004 else {
1005 continue;
1006 };
1007
1008 pctx.set_signature(signature);
1009
1010 let proc_mast_root =
1011 mast_forest_builder.get_mast_node(proc_node_id).unwrap().digest();
1012
1013 let procedure = pctx.into_procedure(proc_mast_root, proc_node_id);
1014
1015 self.linker.register_procedure_root(procedure_gid, proc_mast_root)?;
1017 mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
1018 },
1019 SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
1020 },
1022 }
1023 }
1024
1025 Ok(())
1026 }
1027
1028 fn compile_procedure(
1030 &self,
1031 mut proc_ctx: ProcedureContext,
1032 mast_forest_builder: &mut MastForestBuilder,
1033 ) -> Result<Procedure, Report> {
1034 let gid = proc_ctx.id();
1036
1037 let num_locals = proc_ctx.num_locals();
1038
1039 let proc = match self.linker[gid].item() {
1040 SymbolItem::Procedure(proc) => proc.borrow(),
1041 _ => panic!("expected item to be a procedure AST"),
1042 };
1043 let body_wrapper = if proc_ctx.is_program_entrypoint() {
1044 assert!(num_locals == 0, "program entrypoint cannot have locals");
1045
1046 Some(BodyWrapper {
1047 prologue: fmp_initialization_sequence(),
1048 epilogue: Vec::new(),
1049 })
1050 } else if num_locals > 0 {
1051 Some(BodyWrapper {
1052 prologue: fmp_start_frame_sequence(num_locals),
1053 epilogue: fmp_end_frame_sequence(num_locals),
1054 })
1055 } else {
1056 None
1057 };
1058
1059 let proc_body_id =
1060 self.compile_body(proc.iter(), &mut proc_ctx, body_wrapper, mast_forest_builder, 0)?;
1061
1062 let proc_body_node = mast_forest_builder
1063 .get_mast_node(proc_body_id)
1064 .expect("no MAST node for compiled procedure");
1065 Ok(proc_ctx.into_procedure(proc_body_node.digest(), proc_body_id))
1066 }
1067
1068 fn create_asmop_decorator(
1070 &self,
1071 span: &SourceSpan,
1072 op_name: &str,
1073 proc_ctx: &ProcedureContext,
1074 ) -> AssemblyOp {
1075 let location = proc_ctx.source_manager().location(*span).ok();
1076 let context_name = proc_ctx.path().to_string();
1077 let num_cycles = 0;
1078 AssemblyOp::new(location, context_name, num_cycles, op_name.to_string())
1079 }
1080
1081 fn compile_body<'a, I>(
1082 &self,
1083 body: I,
1084 proc_ctx: &mut ProcedureContext,
1085 wrapper: Option<BodyWrapper>,
1086 mast_forest_builder: &mut MastForestBuilder,
1087 nesting_depth: usize,
1088 ) -> Result<MastNodeId, Report>
1089 where
1090 I: Iterator<Item = &'a ast::Op>,
1091 {
1092 use ast::Op;
1093
1094 let mut body_node_ids: Vec<MastNodeId> = Vec::new();
1095 let mut block_builder = BasicBlockBuilder::new(wrapper, mast_forest_builder);
1096
1097 for op in body {
1098 match op {
1099 Op::Inst(inst) => {
1100 if let Some(node_id) =
1101 self.compile_instruction(inst, &mut block_builder, proc_ctx)?
1102 {
1103 if let Some(basic_block_id) = block_builder.make_basic_block()? {
1104 body_node_ids.push(basic_block_id);
1105 } else if let Some(decorator_ids) = block_builder.drain_decorators() {
1106 block_builder
1107 .mast_forest_builder_mut()
1108 .append_before_enter(node_id, decorator_ids)
1109 .into_diagnostic()?;
1110 }
1111
1112 body_node_ids.push(node_id);
1113 }
1114 },
1115
1116 Op::If { then_blk, else_blk, span } => {
1117 if let Some(basic_block_id) = block_builder.make_basic_block()? {
1118 body_node_ids.push(basic_block_id);
1119 }
1120
1121 let next_depth = nesting_depth + 1;
1122 if next_depth > MAX_CONTROL_FLOW_NESTING {
1123 return Err(Report::new(AssemblerError::ControlFlowNestingDepthExceeded {
1124 span: *span,
1125 source_file: proc_ctx.source_manager().get(span.source_id()).ok(),
1126 max_depth: MAX_CONTROL_FLOW_NESTING,
1127 }));
1128 }
1129
1130 let then_blk = self.compile_body(
1131 then_blk.iter(),
1132 proc_ctx,
1133 None,
1134 block_builder.mast_forest_builder_mut(),
1135 next_depth,
1136 )?;
1137 let else_blk = self.compile_body(
1138 else_blk.iter(),
1139 proc_ctx,
1140 None,
1141 block_builder.mast_forest_builder_mut(),
1142 next_depth,
1143 )?;
1144
1145 let asm_op = self.create_asmop_decorator(span, "if.true", proc_ctx);
1146 let mut split_builder = SplitNodeBuilder::new([then_blk, else_blk]);
1147 if let Some(decorator_ids) = block_builder.drain_decorators() {
1148 split_builder.append_before_enter(decorator_ids);
1149 }
1150
1151 let split_node_id = block_builder
1152 .mast_forest_builder_mut()
1153 .ensure_node_with_asm_op(split_builder, asm_op)?;
1154
1155 body_node_ids.push(split_node_id);
1156 },
1157
1158 Op::Repeat { count, body, span } => {
1159 if let Some(basic_block_id) = block_builder.make_basic_block()? {
1160 body_node_ids.push(basic_block_id);
1161 }
1162
1163 let next_depth = nesting_depth + 1;
1164 if next_depth > MAX_CONTROL_FLOW_NESTING {
1165 return Err(Report::new(AssemblerError::ControlFlowNestingDepthExceeded {
1166 span: *span,
1167 source_file: proc_ctx.source_manager().get(span.source_id()).ok(),
1168 max_depth: MAX_CONTROL_FLOW_NESTING,
1169 }));
1170 }
1171
1172 let repeat_node_id = self.compile_body(
1173 body.iter(),
1174 proc_ctx,
1175 None,
1176 block_builder.mast_forest_builder_mut(),
1177 next_depth,
1178 )?;
1179
1180 let iteration_count = (*count).expect_value();
1181 if iteration_count == 0 {
1182 return Err(RelatedLabel::error("invalid repeat count")
1183 .with_help("repeat count must be greater than 0")
1184 .with_labeled_span(count.span(), "repeat count must be at least 1")
1185 .with_source_file(
1186 proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
1187 )
1188 .into());
1189 }
1190 if iteration_count > MAX_REPEAT_COUNT {
1191 return Err(RelatedLabel::error("invalid repeat count")
1192 .with_help(format!(
1193 "repeat count must be less than or equal to {MAX_REPEAT_COUNT}",
1194 ))
1195 .with_labeled_span(
1196 count.span(),
1197 format!("repeat count exceeds {MAX_REPEAT_COUNT}"),
1198 )
1199 .with_source_file(
1200 proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
1201 )
1202 .into());
1203 }
1204
1205 if let Some(decorator_ids) = block_builder.drain_decorators() {
1206 let first_repeat_builder = block_builder.mast_forest_builder()
1211 [repeat_node_id]
1212 .clone()
1213 .to_builder(block_builder.mast_forest_builder().mast_forest())
1214 .with_before_enter(decorator_ids);
1215 let first_repeat_node_id = block_builder
1216 .mast_forest_builder_mut()
1217 .ensure_node_preserving_debug_vars(
1218 first_repeat_builder,
1219 repeat_node_id,
1220 )?;
1221
1222 body_node_ids.push(first_repeat_node_id);
1223 let remaining_iterations =
1224 iteration_count.checked_sub(1).ok_or_else(|| {
1225 Report::new(
1226 RelatedLabel::error("invalid repeat count")
1227 .with_help("repeat count must be greater than 0")
1228 .with_labeled_span(
1229 count.span(),
1230 "repeat count must be at least 1",
1231 )
1232 .with_source_file(
1233 proc_ctx
1234 .source_manager()
1235 .get(proc_ctx.span().source_id())
1236 .ok(),
1237 ),
1238 )
1239 })?;
1240 for _ in 0..remaining_iterations {
1241 body_node_ids.push(repeat_node_id);
1242 }
1243 } else {
1244 for _ in 0..iteration_count {
1245 body_node_ids.push(repeat_node_id);
1246 }
1247 }
1248 },
1249
1250 Op::While { body, span } => {
1251 if let Some(basic_block_id) = block_builder.make_basic_block()? {
1252 body_node_ids.push(basic_block_id);
1253 }
1254
1255 let next_depth = nesting_depth + 1;
1256 if next_depth > MAX_CONTROL_FLOW_NESTING {
1257 return Err(Report::new(AssemblerError::ControlFlowNestingDepthExceeded {
1258 span: *span,
1259 source_file: proc_ctx.source_manager().get(span.source_id()).ok(),
1260 max_depth: MAX_CONTROL_FLOW_NESTING,
1261 }));
1262 }
1263
1264 let loop_body_node_id = self.compile_body(
1265 body.iter(),
1266 proc_ctx,
1267 None,
1268 block_builder.mast_forest_builder_mut(),
1269 next_depth,
1270 )?;
1271 let mut loop_builder = LoopNodeBuilder::new(loop_body_node_id);
1272 if let Some(decorator_ids) = block_builder.drain_decorators() {
1273 loop_builder.append_before_enter(decorator_ids);
1274 }
1275
1276 let asm_op = self.create_asmop_decorator(span, "while.true", proc_ctx);
1277 let loop_node_id = block_builder
1278 .mast_forest_builder_mut()
1279 .ensure_node_with_asm_op(loop_builder, asm_op)?;
1280
1281 body_node_ids.push(loop_node_id);
1282 },
1283 }
1284 }
1285
1286 let maybe_post_decorators: Option<Vec<DecoratorId>> =
1287 match block_builder.try_into_basic_block()? {
1288 BasicBlockOrDecorators::BasicBlock(basic_block_id) => {
1289 body_node_ids.push(basic_block_id);
1290 None
1291 },
1292 BasicBlockOrDecorators::Decorators(decorator_ids) => {
1293 Some(decorator_ids)
1295 },
1296 BasicBlockOrDecorators::Nothing => None,
1297 };
1298
1299 let procedure_body_id = if body_node_ids.is_empty() {
1300 if maybe_post_decorators.is_some() {
1304 return Err(Report::new(
1305 RelatedLabel::error("invalid procedure")
1306 .with_labeled_span(
1307 proc_ctx.span(),
1308 "body must contain at least one instruction if it has decorators",
1309 )
1310 .with_source_file(
1311 proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
1312 ),
1313 ));
1314 }
1315
1316 mast_forest_builder.ensure_block(
1317 vec![Operation::Noop],
1318 Vec::new(),
1319 vec![],
1320 vec![],
1321 vec![],
1322 vec![],
1323 )?
1324 } else {
1325 let asm_op = self.create_asmop_decorator(&proc_ctx.span(), "begin", proc_ctx);
1326 mast_forest_builder.join_nodes(body_node_ids, Some(asm_op))?
1327 };
1328
1329 if let Some(post_decorator_ids) = maybe_post_decorators {
1331 mast_forest_builder
1332 .append_after_exit(procedure_body_id, post_decorator_ids)
1333 .into_diagnostic()?;
1334 }
1335
1336 Ok(procedure_body_id)
1337 }
1338
1339 pub(super) fn resolve_target(
1346 &self,
1347 kind: InvokeKind,
1348 target: &InvocationTarget,
1349 caller_id: GlobalItemIndex,
1350 mast_forest_builder: &mut MastForestBuilder,
1351 ) -> Result<Option<ResolvedProcedure>, Report> {
1352 let caller = SymbolResolutionContext {
1353 span: target.span(),
1354 module: caller_id.module,
1355 kind: Some(kind),
1356 };
1357 let resolved = self.linker.resolve_invoke_target(&caller, target)?;
1358 match resolved {
1359 SymbolResolution::MastRoot(mast_root) => {
1360 let node = self.ensure_valid_procedure_mast_root(
1361 kind,
1362 target.span(),
1363 mast_root.into_inner(),
1364 mast_forest_builder,
1365 )?;
1366 Ok(Some(ResolvedProcedure { node, signature: None }))
1367 },
1368 SymbolResolution::Exact { gid, .. } => {
1369 match mast_forest_builder.get_procedure(gid) {
1370 Some(proc) => Ok(Some(ResolvedProcedure {
1371 node: proc.body_node_id(),
1372 signature: proc.signature(),
1373 })),
1374 None => match self.linker[gid].item() {
1377 SymbolItem::Compiled(ItemInfo::Procedure(p)) => {
1378 let node = self.ensure_valid_procedure_mast_root(
1379 kind,
1380 target.span(),
1381 p.digest,
1382 mast_forest_builder,
1383 )?;
1384 Ok(Some(ResolvedProcedure { node, signature: p.signature.clone() }))
1385 },
1386 SymbolItem::Procedure(_) => panic!(
1387 "AST procedure {gid:?} exists in the linker, but not in the MastForestBuilder"
1388 ),
1389 SymbolItem::Alias { .. } => {
1390 unreachable!("unexpected reference to ast alias item from {gid:?}")
1391 },
1392 SymbolItem::Compiled(_) | SymbolItem::Type(_) | SymbolItem::Constant(_) => {
1393 Ok(None)
1394 },
1395 },
1396 }
1397 },
1398 SymbolResolution::Module { .. }
1399 | SymbolResolution::External(_)
1400 | SymbolResolution::Local(_) => unreachable!(),
1401 }
1402 }
1403
1404 fn ensure_valid_procedure_mast_root(
1409 &self,
1410 kind: InvokeKind,
1411 span: SourceSpan,
1412 mast_root: Word,
1413 mast_forest_builder: &mut MastForestBuilder,
1414 ) -> Result<MastNodeId, Report> {
1415 let current_source_file = self.source_manager.get(span.source_id()).ok();
1417
1418 if matches!(kind, InvokeKind::SysCall) && self.linker.has_nonempty_kernel() {
1419 if !self.linker.kernel().contains_proc(mast_root) {
1423 let callee = mast_forest_builder
1424 .find_procedure_by_mast_root(&mast_root)
1425 .map(|proc| proc.path().clone())
1426 .unwrap_or_else(|| {
1427 let digest_path = format!("{mast_root}");
1428 Arc::<Path>::from(Path::new(&digest_path))
1429 });
1430 return Err(Report::new(LinkerError::InvalidSysCallTarget {
1431 span,
1432 source_file: current_source_file,
1433 callee,
1434 }));
1435 }
1436 }
1437
1438 mast_forest_builder.ensure_external_link(mast_root)
1439 }
1440}
1441
1442struct SubgraphRoot {
1450 proc_id: GlobalItemIndex,
1451 is_program_entrypoint: bool,
1452}
1453
1454impl SubgraphRoot {
1455 fn with_entrypoint(proc_id: GlobalItemIndex) -> Self {
1456 Self { proc_id, is_program_entrypoint: true }
1457 }
1458
1459 fn not_as_entrypoint(proc_id: GlobalItemIndex) -> Self {
1460 Self { proc_id, is_program_entrypoint: false }
1461 }
1462}
1463
1464pub(crate) struct BodyWrapper {
1467 pub prologue: Vec<Operation>,
1468 pub epilogue: Vec<Operation>,
1469}
1470
1471pub(super) struct ResolvedProcedure {
1472 pub node: MastNodeId,
1473 pub signature: Option<Arc<FunctionType>>,
1474}