1use alloc::{collections::BTreeMap, string::ToString, sync::Arc, vec::Vec};
2
3use basic_block_builder::BasicBlockOrDecorators;
4use mast_forest_builder::MastForestBuilder;
5use module_graph::{ProcedureWrapper, WrappedModule};
6use vm_core::{
7 AssemblyOp, Decorator, 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 .append_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, span } => {
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 .append_before_enter(split_node_id, &decorator_ids)
653 }
654
655 if self.in_debug_mode() {
657 let location = proc_ctx.source_manager().location(*span).ok();
658 let context_name = proc_ctx.name().to_string();
659 let num_cycles = 0;
660 let op = "if.true".to_string();
661 let should_break = false;
662 let op =
663 AssemblyOp::new(location, context_name, num_cycles, op, should_break);
664 let decorator_id = block_builder
665 .mast_forest_builder_mut()
666 .ensure_decorator(Decorator::AsmOp(op))?;
667 block_builder
668 .mast_forest_builder_mut()
669 .append_before_enter(split_node_id, &[decorator_id]);
670 }
671
672 body_node_ids.push(split_node_id);
673 },
674
675 Op::Repeat { count, body, .. } => {
676 if let Some(basic_block_id) = block_builder.make_basic_block()? {
677 body_node_ids.push(basic_block_id);
678 }
679
680 let repeat_node_id = self.compile_body(
681 body.iter(),
682 proc_ctx,
683 None,
684 block_builder.mast_forest_builder_mut(),
685 )?;
686
687 if let Some(decorator_ids) = block_builder.drain_decorators() {
688 let mut first_repeat_node =
690 block_builder.mast_forest_builder_mut()[repeat_node_id].clone();
691 first_repeat_node.append_before_enter(&decorator_ids);
692 let first_repeat_node_id = block_builder
693 .mast_forest_builder_mut()
694 .ensure_node(first_repeat_node)?;
695
696 body_node_ids.push(first_repeat_node_id);
697 for _ in 0..(*count - 1) {
698 body_node_ids.push(repeat_node_id);
699 }
700 } else {
701 for _ in 0..*count {
702 body_node_ids.push(repeat_node_id);
703 }
704 }
705 },
706
707 Op::While { body, span } => {
708 if let Some(basic_block_id) = block_builder.make_basic_block()? {
709 body_node_ids.push(basic_block_id);
710 }
711
712 let loop_node_id = {
713 let loop_body_node_id = self.compile_body(
714 body.iter(),
715 proc_ctx,
716 None,
717 block_builder.mast_forest_builder_mut(),
718 )?;
719 block_builder.mast_forest_builder_mut().ensure_loop(loop_body_node_id)?
720 };
721 if let Some(decorator_ids) = block_builder.drain_decorators() {
722 block_builder
723 .mast_forest_builder_mut()
724 .append_before_enter(loop_node_id, &decorator_ids)
725 }
726
727 if self.in_debug_mode() {
729 let location = proc_ctx.source_manager().location(*span).ok();
730 let context_name = proc_ctx.name().to_string();
731 let num_cycles = 0;
732 let op = "while.true".to_string();
733 let should_break = false;
734 let op =
735 AssemblyOp::new(location, context_name, num_cycles, op, should_break);
736 let decorator_id = block_builder
737 .mast_forest_builder_mut()
738 .ensure_decorator(Decorator::AsmOp(op))?;
739 block_builder
740 .mast_forest_builder_mut()
741 .append_before_enter(loop_node_id, &[decorator_id]);
742 }
743
744 body_node_ids.push(loop_node_id);
745 },
746 }
747 }
748
749 let maybe_post_decorators: Option<Vec<DecoratorId>> =
750 match block_builder.try_into_basic_block()? {
751 BasicBlockOrDecorators::BasicBlock(basic_block_id) => {
752 body_node_ids.push(basic_block_id);
753 None
754 },
755 BasicBlockOrDecorators::Decorators(decorator_ids) => {
756 Some(decorator_ids)
758 },
759 BasicBlockOrDecorators::Nothing => None,
760 };
761
762 let procedure_body_id = if body_node_ids.is_empty() {
763 if maybe_post_decorators.is_some() {
767 return Err(AssemblyError::EmptyProcedureBodyWithDecorators {
768 span: proc_ctx.span(),
769 source_file: proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
770 })?;
771 }
772
773 mast_forest_builder.ensure_block(vec![Operation::Noop], None)?
774 } else {
775 mast_forest_builder.join_nodes(body_node_ids)?
776 };
777
778 if let Some(post_decorator_ids) = maybe_post_decorators {
780 mast_forest_builder.append_after_exit(procedure_body_id, &post_decorator_ids);
781 }
782
783 Ok(procedure_body_id)
784 }
785
786 pub(super) fn resolve_target(
791 &self,
792 kind: InvokeKind,
793 target: &InvocationTarget,
794 proc_ctx: &ProcedureContext,
795 mast_forest_builder: &mut MastForestBuilder,
796 ) -> Result<MastNodeId, AssemblyError> {
797 let caller = CallerInfo {
798 span: target.span(),
799 module: proc_ctx.id().module,
800 kind,
801 };
802 let resolved = self.module_graph.resolve_target(&caller, target)?;
803 match resolved {
804 ResolvedTarget::Phantom(mast_root) => self.ensure_valid_procedure_mast_root(
805 kind,
806 target.span(),
807 mast_root,
808 mast_forest_builder,
809 ),
810 ResolvedTarget::Exact { gid } | ResolvedTarget::Resolved { gid, .. } => {
811 match mast_forest_builder.get_procedure(gid) {
812 Some(proc) => Ok(proc.body_node_id()),
813 None => match self.module_graph.get_procedure_unsafe(gid) {
816 ProcedureWrapper::Info(p) => self.ensure_valid_procedure_mast_root(
817 kind,
818 target.span(),
819 p.digest,
820 mast_forest_builder,
821 ),
822 ProcedureWrapper::Ast(_) => panic!(
823 "AST procedure {gid:?} exits in the module graph but not in the MastForestBuilder"
824 ),
825 },
826 }
827 },
828 }
829 }
830
831 fn ensure_valid_procedure_mast_root(
836 &self,
837 kind: InvokeKind,
838 span: SourceSpan,
839 mast_root: RpoDigest,
840 mast_forest_builder: &mut MastForestBuilder,
841 ) -> Result<MastNodeId, AssemblyError> {
842 let current_source_file = self.source_manager.get(span.source_id()).ok();
844
845 match mast_forest_builder.find_procedure_by_mast_root(&mast_root) {
847 Some(proc) if matches!(kind, InvokeKind::SysCall) => {
848 if !proc.visibility().is_syscall() {
854 return Err(AssemblyError::InvalidSysCallTarget {
855 span,
856 source_file: current_source_file,
857 callee: proc.fully_qualified_name().clone(),
858 });
859 }
860 let maybe_kernel_path = proc.path();
861 self.module_graph
862 .find_module(maybe_kernel_path)
863 .ok_or_else(|| AssemblyError::InvalidSysCallTarget {
864 span,
865 source_file: current_source_file.clone(),
866 callee: proc.fully_qualified_name().clone(),
867 })
868 .and_then(|module| {
869 if module.unwrap_ast().is_kernel() {
873 Ok(())
874 } else {
875 Err(AssemblyError::InvalidSysCallTarget {
876 span,
877 source_file: current_source_file.clone(),
878 callee: proc.fully_qualified_name().clone(),
879 })
880 }
881 })?;
882 },
883 Some(_) | None => (),
884 }
885
886 mast_forest_builder.vendor_or_ensure_external(mast_root)
887 }
888}
889
890struct BodyWrapper {
896 prologue: Vec<Operation>,
897 epilogue: Vec<Operation>,
898}