1use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
2
3use basic_block_builder::BasicBlockOrDecorators;
4use mast_forest_builder::MastForestBuilder;
5use module_graph::{ProcedureWrapper, WrappedModule};
6use vm_core::{
7 DecoratorList, Felt, Kernel, Operation, Program, WORD_SIZE,
8 crypto::hash::RpoDigest,
9 debuginfo::SourceSpan,
10 mast::{DecoratorId, MastNodeId},
11};
12
13use crate::{
14 AssemblyError, Compile, CompileOptions, LibraryNamespace, LibraryPath, SourceManager, Spanned,
15 ast::{self, Export, InvocationTarget, InvokeKind, ModuleKind, QualifiedProcedureName},
16 diagnostics::Report,
17 library::{KernelLibrary, Library},
18 sema::SemanticAnalysisError,
19};
20
21mod basic_block_builder;
22mod id;
23mod instruction;
24mod mast_forest_builder;
25mod module_graph;
26mod procedure;
27
28#[cfg(test)]
29mod tests;
30
31#[cfg(test)]
32mod mast_forest_merger_tests;
33
34use self::{
35 basic_block_builder::BasicBlockBuilder,
36 module_graph::{CallerInfo, ModuleGraph, ResolvedTarget},
37};
38pub use self::{
39 id::{GlobalProcedureIndex, ModuleIndex},
40 procedure::{Procedure, ProcedureContext},
41};
42
43#[derive(Clone)]
65pub struct Assembler {
66 source_manager: Arc<dyn SourceManager>,
68 module_graph: ModuleGraph,
70 warnings_as_errors: bool,
72 in_debug_mode: bool,
74 vendored_libraries: BTreeMap<RpoDigest, Library>,
76}
77
78impl Default for Assembler {
79 fn default() -> Self {
80 let source_manager = Arc::new(crate::DefaultSourceManager::default());
81 let module_graph = ModuleGraph::new(source_manager.clone());
82 Self {
83 source_manager,
84 module_graph,
85 warnings_as_errors: false,
86 in_debug_mode: false,
87 vendored_libraries: BTreeMap::new(),
88 }
89 }
90}
91
92impl Assembler {
95 pub fn new(source_manager: Arc<dyn SourceManager>) -> Self {
97 let module_graph = ModuleGraph::new(source_manager.clone());
98 Self {
99 source_manager,
100 module_graph,
101 warnings_as_errors: false,
102 in_debug_mode: false,
103 vendored_libraries: BTreeMap::new(),
104 }
105 }
106
107 pub fn with_kernel(source_manager: Arc<dyn SourceManager>, kernel_lib: KernelLibrary) -> Self {
109 let (kernel, kernel_module, _) = kernel_lib.into_parts();
110 let module_graph = ModuleGraph::with_kernel(source_manager.clone(), kernel, kernel_module);
111 Self {
112 source_manager,
113 module_graph,
114 ..Default::default()
115 }
116 }
117
118 pub fn with_warnings_as_errors(mut self, yes: bool) -> Self {
122 self.warnings_as_errors = yes;
123 self
124 }
125
126 pub fn with_debug_mode(mut self, yes: bool) -> Self {
128 self.in_debug_mode = yes;
129 self
130 }
131
132 pub fn set_debug_mode(&mut self, yes: bool) {
134 self.in_debug_mode = yes;
135 }
136
137 #[inline]
141 pub fn with_module(mut self, module: impl Compile) -> Result<Self, Report> {
142 self.add_module(module)?;
143
144 Ok(self)
145 }
146
147 #[inline]
151 pub fn with_module_and_options(
152 mut self,
153 module: impl Compile,
154 options: CompileOptions,
155 ) -> Result<Self, Report> {
156 self.add_module_with_options(module, options)?;
157
158 Ok(self)
159 }
160
161 #[inline]
165 pub fn add_module(&mut self, module: impl Compile) -> Result<ModuleIndex, Report> {
166 self.add_module_with_options(module, CompileOptions::for_library())
167 }
168
169 pub fn add_module_with_options(
173 &mut self,
174 module: impl Compile,
175 options: CompileOptions,
176 ) -> Result<ModuleIndex, Report> {
177 let ids = self.add_modules_with_options([module], options)?;
178 Ok(ids[0])
179 }
180
181 pub fn add_modules_with_options(
185 &mut self,
186 modules: impl IntoIterator<Item = impl Compile>,
187 options: CompileOptions,
188 ) -> Result<Vec<ModuleIndex>, Report> {
189 let kind = options.kind;
190 if kind == ModuleKind::Executable {
191 return Err(Report::msg("Executables are not supported by `add_module_with_options`"));
192 }
193
194 let modules = modules
195 .into_iter()
196 .map(|module| {
197 let module = module.compile_with_options(&self.source_manager, options.clone())?;
198 assert_eq!(
199 module.kind(),
200 kind,
201 "expected module kind to match compilation options"
202 );
203 Ok(module)
204 })
205 .collect::<Result<Vec<_>, Report>>()?;
206 let ids = self.module_graph.add_ast_modules(modules)?;
207 Ok(ids)
208 }
209 #[cfg(feature = "std")]
221 pub fn add_modules_from_dir(
222 &mut self,
223 namespace: crate::LibraryNamespace,
224 dir: &std::path::Path,
225 ) -> Result<(), Report> {
226 let modules = crate::parser::read_modules_from_dir(namespace, dir, &self.source_manager)?;
227 self.module_graph.add_ast_modules(modules)?;
228 Ok(())
229 }
230
231 pub fn add_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> {
246 self.module_graph
247 .add_compiled_modules(library.as_ref().module_infos())
248 .map_err(Report::from)?;
249 Ok(())
250 }
251
252 pub fn with_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
256 self.add_library(library)?;
257 Ok(self)
258 }
259
260 pub fn add_vendored_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> {
267 self.add_library(&library)?;
268 self.vendored_libraries
269 .insert(*library.as_ref().digest(), library.as_ref().clone());
270 Ok(())
271 }
272
273 pub fn with_vendored_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
277 self.add_vendored_library(library)?;
278 Ok(self)
279 }
280}
281
282impl Assembler {
285 pub fn warnings_as_errors(&self) -> bool {
287 self.warnings_as_errors
288 }
289
290 pub fn in_debug_mode(&self) -> bool {
292 self.in_debug_mode
293 }
294
295 pub fn kernel(&self) -> &Kernel {
299 self.module_graph.kernel()
300 }
301
302 pub fn source_manager(&self) -> Arc<dyn SourceManager> {
304 self.source_manager.clone()
305 }
306
307 #[cfg(any(test, feature = "testing"))]
308 #[doc(hidden)]
309 pub fn module_graph(&self) -> &ModuleGraph {
310 &self.module_graph
311 }
312}
313
314impl Assembler {
317 fn assemble_common(
319 mut self,
320 modules: impl IntoIterator<Item = impl Compile>,
321 options: CompileOptions,
322 ) -> Result<Library, Report> {
323 let mut mast_forest_builder = MastForestBuilder::new(self.vendored_libraries.values())?;
324
325 let ast_module_indices = self.add_modules_with_options(modules, options)?;
326
327 let mut exports = {
328 let mut exports = BTreeMap::new();
329
330 for module_idx in ast_module_indices {
331 let ast_module = self.module_graph[module_idx].unwrap_ast().clone();
334
335 for (proc_idx, fqn) in ast_module.exported_procedures() {
336 let gid = module_idx + proc_idx;
337 self.compile_subgraph(gid, &mut mast_forest_builder)?;
338
339 let proc_root_node_id = mast_forest_builder
340 .get_procedure(gid)
341 .expect("compilation succeeded but root not found in cache")
342 .body_node_id();
343 exports.insert(fqn, proc_root_node_id);
344 }
345 }
346
347 exports
348 };
349
350 let (mast_forest, id_remappings) = mast_forest_builder.build();
351 for (_proc_name, node_id) in exports.iter_mut() {
352 if let Some(&new_node_id) = id_remappings.get(node_id) {
353 *node_id = new_node_id;
354 }
355 }
356
357 Ok(Library::new(mast_forest.into(), exports)?)
358 }
359
360 pub fn assemble_library(
366 self,
367 modules: impl IntoIterator<Item = impl Compile>,
368 ) -> Result<Library, Report> {
369 let options = CompileOptions {
370 kind: ModuleKind::Library,
371 warnings_as_errors: self.warnings_as_errors,
372 path: None,
373 };
374 self.assemble_common(modules, options)
375 }
376
377 pub fn assemble_kernel(self, module: impl Compile) -> Result<KernelLibrary, Report> {
383 let options = CompileOptions {
384 kind: ModuleKind::Kernel,
385 warnings_as_errors: self.warnings_as_errors,
386 path: Some(LibraryPath::from(LibraryNamespace::Kernel)),
387 };
388 let library = self.assemble_common([module], options)?;
389 Ok(library.try_into()?)
390 }
391
392 pub fn assemble_program(mut self, source: impl Compile) -> Result<Program, Report> {
400 let options = CompileOptions {
401 kind: ModuleKind::Executable,
402 warnings_as_errors: self.warnings_as_errors,
403 path: Some(LibraryPath::from(LibraryNamespace::Exec)),
404 };
405
406 let program = source.compile_with_options(&self.source_manager, options)?;
407 assert!(program.is_executable());
408
409 let ast_module_index = self.module_graph.add_ast_module(program)?;
411
412 let entrypoint = self.module_graph[ast_module_index]
415 .unwrap_ast()
416 .index_of(|p| p.is_main())
417 .map(|index| GlobalProcedureIndex { module: ast_module_index, index })
418 .ok_or(SemanticAnalysisError::MissingEntrypoint)?;
419
420 let mut mast_forest_builder = MastForestBuilder::new(self.vendored_libraries.values())?;
422
423 self.compile_subgraph(entrypoint, &mut mast_forest_builder)?;
424 let entry_node_id = mast_forest_builder
425 .get_procedure(entrypoint)
426 .expect("compilation succeeded but root not found in cache")
427 .body_node_id();
428
429 let (mast_forest, id_remappings) = mast_forest_builder.build();
431 let entry_node_id = *id_remappings.get(&entry_node_id).unwrap_or(&entry_node_id);
432
433 Ok(Program::with_kernel(
434 mast_forest.into(),
435 entry_node_id,
436 self.module_graph.kernel().clone(),
437 ))
438 }
439
440 fn compile_subgraph(
445 &mut self,
446 root: GlobalProcedureIndex,
447 mast_forest_builder: &mut MastForestBuilder,
448 ) -> Result<(), Report> {
449 let mut worklist: Vec<GlobalProcedureIndex> = self
450 .module_graph
451 .topological_sort_from_root(root)
452 .map_err(|cycle| {
453 let iter = cycle.into_node_ids();
454 let mut nodes = Vec::with_capacity(iter.len());
455 for node in iter {
456 let module = self.module_graph[node.module].path();
457 let proc = self.module_graph.get_procedure_unsafe(node);
458 nodes.push(format!("{}::{}", module, proc.name()));
459 }
460 AssemblyError::Cycle { nodes }
461 })?
462 .into_iter()
463 .filter(|&gid| self.module_graph.get_procedure_unsafe(gid).is_ast())
464 .collect();
465
466 assert!(!worklist.is_empty());
467
468 self.process_graph_worklist(&mut worklist, mast_forest_builder)
469 }
470
471 fn process_graph_worklist(
473 &mut self,
474 worklist: &mut Vec<GlobalProcedureIndex>,
475 mast_forest_builder: &mut MastForestBuilder,
476 ) -> Result<(), Report> {
477 while let Some(procedure_gid) = worklist.pop() {
480 if let Some(proc) = mast_forest_builder.get_procedure(procedure_gid) {
482 self.module_graph.register_procedure_root(procedure_gid, proc.mast_root())?;
483 continue;
484 }
485 let module = match &self.module_graph[procedure_gid.module] {
487 WrappedModule::Ast(ast_module) => ast_module,
488 WrappedModule::Info(_) => continue,
491 };
492
493 let export = &module[procedure_gid.index];
494 match export {
495 Export::Procedure(proc) => {
496 let num_locals = proc.num_locals();
497 let name = QualifiedProcedureName {
498 span: proc.span(),
499 module: module.path().clone(),
500 name: proc.name().clone(),
501 };
502 let pctx = ProcedureContext::new(
503 procedure_gid,
504 name,
505 proc.visibility(),
506 module.is_kernel(),
507 self.source_manager.clone(),
508 )
509 .with_num_locals(num_locals)
510 .with_span(proc.span());
511
512 let procedure = self.compile_procedure(pctx, mast_forest_builder)?;
514 self.module_graph
522 .register_procedure_root(procedure_gid, procedure.mast_root())?;
523 mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
524 },
525 Export::Alias(proc_alias) => {
526 let name = QualifiedProcedureName {
527 span: proc_alias.span(),
528 module: module.path().clone(),
529 name: proc_alias.name().clone(),
530 };
531 let pctx = ProcedureContext::new(
532 procedure_gid,
533 name,
534 ast::Visibility::Public,
535 module.is_kernel(),
536 self.source_manager.clone(),
537 )
538 .with_span(proc_alias.span());
539
540 let proc_node_id = self.resolve_target(
541 InvokeKind::ProcRef,
542 &proc_alias.target().into(),
543 &pctx,
544 mast_forest_builder,
545 )?;
546 let proc_mast_root =
547 mast_forest_builder.get_mast_node(proc_node_id).unwrap().digest();
548
549 let procedure = pctx.into_procedure(proc_mast_root, proc_node_id);
550
551 self.module_graph.register_procedure_root(procedure_gid, proc_mast_root)?;
553 mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
554 },
555 }
556 }
557
558 Ok(())
559 }
560
561 fn compile_procedure(
563 &self,
564 mut proc_ctx: ProcedureContext,
565 mast_forest_builder: &mut MastForestBuilder,
566 ) -> Result<Procedure, Report> {
567 let gid = proc_ctx.id();
569
570 let num_locals = proc_ctx.num_locals();
571
572 let wrapper_proc = self.module_graph.get_procedure_unsafe(gid);
573 let proc = wrapper_proc.unwrap_ast().unwrap_procedure();
574 let proc_body_id = if num_locals > 0 {
575 let locals_frame = Felt::from(num_locals.next_multiple_of(WORD_SIZE as u16));
581 let wrapper = BodyWrapper {
582 prologue: vec![Operation::Push(locals_frame), Operation::FmpUpdate],
583 epilogue: vec![Operation::Push(-locals_frame), Operation::FmpUpdate],
584 };
585 self.compile_body(proc.iter(), &mut proc_ctx, Some(wrapper), mast_forest_builder)?
586 } else {
587 self.compile_body(proc.iter(), &mut proc_ctx, None, mast_forest_builder)?
588 };
589
590 let proc_body_node = mast_forest_builder
591 .get_mast_node(proc_body_id)
592 .expect("no MAST node for compiled procedure");
593 Ok(proc_ctx.into_procedure(proc_body_node.digest(), proc_body_id))
594 }
595
596 fn compile_body<'a, I>(
597 &self,
598 body: I,
599 proc_ctx: &mut ProcedureContext,
600 wrapper: Option<BodyWrapper>,
601 mast_forest_builder: &mut MastForestBuilder,
602 ) -> Result<MastNodeId, Report>
603 where
604 I: Iterator<Item = &'a ast::Op>,
605 {
606 use ast::Op;
607
608 let mut body_node_ids: Vec<MastNodeId> = Vec::new();
609 let mut block_builder = BasicBlockBuilder::new(wrapper, mast_forest_builder);
610
611 for op in body {
612 match op {
613 Op::Inst(inst) => {
614 if let Some(node_id) =
615 self.compile_instruction(inst, &mut block_builder, proc_ctx)?
616 {
617 if let Some(basic_block_id) = block_builder.make_basic_block()? {
618 body_node_ids.push(basic_block_id);
619 } else if let Some(decorator_ids) = block_builder.drain_decorators() {
620 block_builder
621 .mast_forest_builder_mut()
622 .set_before_enter(node_id, decorator_ids);
623 }
624
625 body_node_ids.push(node_id);
626 }
627 },
628
629 Op::If { then_blk, else_blk, .. } => {
630 if let Some(basic_block_id) = block_builder.make_basic_block()? {
631 body_node_ids.push(basic_block_id);
632 }
633
634 let then_blk = self.compile_body(
635 then_blk.iter(),
636 proc_ctx,
637 None,
638 block_builder.mast_forest_builder_mut(),
639 )?;
640 let else_blk = self.compile_body(
641 else_blk.iter(),
642 proc_ctx,
643 None,
644 block_builder.mast_forest_builder_mut(),
645 )?;
646
647 let split_node_id =
648 block_builder.mast_forest_builder_mut().ensure_split(then_blk, else_blk)?;
649 if let Some(decorator_ids) = block_builder.drain_decorators() {
650 block_builder
651 .mast_forest_builder_mut()
652 .set_before_enter(split_node_id, decorator_ids)
653 }
654
655 body_node_ids.push(split_node_id);
656 },
657
658 Op::Repeat { count, body, .. } => {
659 if let Some(basic_block_id) = block_builder.make_basic_block()? {
660 body_node_ids.push(basic_block_id);
661 }
662
663 let repeat_node_id = self.compile_body(
664 body.iter(),
665 proc_ctx,
666 None,
667 block_builder.mast_forest_builder_mut(),
668 )?;
669
670 if let Some(decorator_ids) = block_builder.drain_decorators() {
671 let mut first_repeat_node =
673 block_builder.mast_forest_builder_mut()[repeat_node_id].clone();
674 first_repeat_node.set_before_enter(decorator_ids);
675 let first_repeat_node_id = block_builder
676 .mast_forest_builder_mut()
677 .ensure_node(first_repeat_node)?;
678
679 body_node_ids.push(first_repeat_node_id);
680 for _ in 0..(*count - 1) {
681 body_node_ids.push(repeat_node_id);
682 }
683 } else {
684 for _ in 0..*count {
685 body_node_ids.push(repeat_node_id);
686 }
687 }
688 },
689
690 Op::While { body, .. } => {
691 if let Some(basic_block_id) = block_builder.make_basic_block()? {
692 body_node_ids.push(basic_block_id);
693 }
694
695 let loop_node_id = {
696 let loop_body_node_id = self.compile_body(
697 body.iter(),
698 proc_ctx,
699 None,
700 block_builder.mast_forest_builder_mut(),
701 )?;
702 block_builder.mast_forest_builder_mut().ensure_loop(loop_body_node_id)?
703 };
704 if let Some(decorator_ids) = block_builder.drain_decorators() {
705 block_builder
706 .mast_forest_builder_mut()
707 .set_before_enter(loop_node_id, decorator_ids)
708 }
709
710 body_node_ids.push(loop_node_id);
711 },
712 }
713 }
714
715 let maybe_post_decorators: Option<Vec<DecoratorId>> =
716 match block_builder.try_into_basic_block()? {
717 BasicBlockOrDecorators::BasicBlock(basic_block_id) => {
718 body_node_ids.push(basic_block_id);
719 None
720 },
721 BasicBlockOrDecorators::Decorators(decorator_ids) => {
722 Some(decorator_ids)
724 },
725 BasicBlockOrDecorators::Nothing => None,
726 };
727
728 let procedure_body_id = if body_node_ids.is_empty() {
729 if maybe_post_decorators.is_some() {
733 return Err(AssemblyError::EmptyProcedureBodyWithDecorators {
734 span: proc_ctx.span(),
735 source_file: proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
736 })?;
737 }
738
739 mast_forest_builder.ensure_block(vec![Operation::Noop], None)?
740 } else {
741 mast_forest_builder.join_nodes(body_node_ids)?
742 };
743
744 if let Some(post_decorator_ids) = maybe_post_decorators {
746 mast_forest_builder.set_after_exit(procedure_body_id, post_decorator_ids);
747 }
748
749 Ok(procedure_body_id)
750 }
751
752 pub(super) fn resolve_target(
757 &self,
758 kind: InvokeKind,
759 target: &InvocationTarget,
760 proc_ctx: &ProcedureContext,
761 mast_forest_builder: &mut MastForestBuilder,
762 ) -> Result<MastNodeId, AssemblyError> {
763 let caller = CallerInfo {
764 span: target.span(),
765 module: proc_ctx.id().module,
766 kind,
767 };
768 let resolved = self.module_graph.resolve_target(&caller, target)?;
769 match resolved {
770 ResolvedTarget::Phantom(mast_root) => self.ensure_valid_procedure_mast_root(
771 kind,
772 target.span(),
773 mast_root,
774 mast_forest_builder,
775 ),
776 ResolvedTarget::Exact { gid } | ResolvedTarget::Resolved { gid, .. } => {
777 match mast_forest_builder.get_procedure(gid) {
778 Some(proc) => Ok(proc.body_node_id()),
779 None => match self.module_graph.get_procedure_unsafe(gid) {
782 ProcedureWrapper::Info(p) => self.ensure_valid_procedure_mast_root(
783 kind,
784 target.span(),
785 p.digest,
786 mast_forest_builder,
787 ),
788 ProcedureWrapper::Ast(_) => panic!(
789 "AST procedure {gid:?} exits in the module graph but not in the MastForestBuilder"
790 ),
791 },
792 }
793 },
794 }
795 }
796
797 fn ensure_valid_procedure_mast_root(
802 &self,
803 kind: InvokeKind,
804 span: SourceSpan,
805 mast_root: RpoDigest,
806 mast_forest_builder: &mut MastForestBuilder,
807 ) -> Result<MastNodeId, AssemblyError> {
808 let current_source_file = self.source_manager.get(span.source_id()).ok();
810
811 match mast_forest_builder.find_procedure_by_mast_root(&mast_root) {
813 Some(proc) if matches!(kind, InvokeKind::SysCall) => {
814 if !proc.visibility().is_syscall() {
820 return Err(AssemblyError::InvalidSysCallTarget {
821 span,
822 source_file: current_source_file,
823 callee: proc.fully_qualified_name().clone(),
824 });
825 }
826 let maybe_kernel_path = proc.path();
827 self.module_graph
828 .find_module(maybe_kernel_path)
829 .ok_or_else(|| AssemblyError::InvalidSysCallTarget {
830 span,
831 source_file: current_source_file.clone(),
832 callee: proc.fully_qualified_name().clone(),
833 })
834 .and_then(|module| {
835 if module.unwrap_ast().is_kernel() {
839 Ok(())
840 } else {
841 Err(AssemblyError::InvalidSysCallTarget {
842 span,
843 source_file: current_source_file.clone(),
844 callee: proc.fully_qualified_name().clone(),
845 })
846 }
847 })?;
848 },
849 Some(_) | None => (),
850 }
851
852 mast_forest_builder.vendor_or_ensure_external(mast_root)
853 }
854}
855
856struct BodyWrapper {
862 prologue: Vec<Operation>,
863 epilogue: Vec<Operation>,
864}