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 + Send + Sync>,
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 + Send + Sync>) -> 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(
109 source_manager: Arc<dyn SourceManager + Send + Sync>,
110 kernel_lib: KernelLibrary,
111 ) -> Self {
112 let (kernel, kernel_module, _) = kernel_lib.into_parts();
113 let module_graph = ModuleGraph::with_kernel(source_manager.clone(), kernel, kernel_module);
114 Self {
115 source_manager,
116 module_graph,
117 ..Default::default()
118 }
119 }
120
121 pub fn with_warnings_as_errors(mut self, yes: bool) -> Self {
125 self.warnings_as_errors = yes;
126 self
127 }
128
129 pub fn with_debug_mode(mut self, yes: bool) -> Self {
131 self.in_debug_mode = yes;
132 self
133 }
134
135 pub fn set_debug_mode(&mut self, yes: bool) {
137 self.in_debug_mode = yes;
138 }
139
140 #[inline]
144 pub fn with_module(mut self, module: impl Compile) -> Result<Self, Report> {
145 self.add_module(module)?;
146
147 Ok(self)
148 }
149
150 #[inline]
154 pub fn with_module_and_options(
155 mut self,
156 module: impl Compile,
157 options: CompileOptions,
158 ) -> Result<Self, Report> {
159 self.add_module_with_options(module, options)?;
160
161 Ok(self)
162 }
163
164 #[inline]
168 pub fn add_module(&mut self, module: impl Compile) -> Result<ModuleIndex, Report> {
169 self.add_module_with_options(module, CompileOptions::for_library())
170 }
171
172 pub fn add_module_with_options(
176 &mut self,
177 module: impl Compile,
178 options: CompileOptions,
179 ) -> Result<ModuleIndex, Report> {
180 let ids = self.add_modules_with_options([module], options)?;
181 Ok(ids[0])
182 }
183
184 pub fn add_modules_with_options(
188 &mut self,
189 modules: impl IntoIterator<Item = impl Compile>,
190 options: CompileOptions,
191 ) -> Result<Vec<ModuleIndex>, Report> {
192 let kind = options.kind;
193 if kind == ModuleKind::Executable {
194 return Err(Report::msg("Executables are not supported by `add_module_with_options`"));
195 }
196
197 let modules = modules
198 .into_iter()
199 .map(|module| {
200 let module = module.compile_with_options(&self.source_manager, options.clone())?;
201 assert_eq!(
202 module.kind(),
203 kind,
204 "expected module kind to match compilation options"
205 );
206 Ok(module)
207 })
208 .collect::<Result<Vec<_>, Report>>()?;
209 let ids = self.module_graph.add_ast_modules(modules)?;
210 Ok(ids)
211 }
212 #[cfg(feature = "std")]
224 pub fn add_modules_from_dir(
225 &mut self,
226 namespace: crate::LibraryNamespace,
227 dir: &std::path::Path,
228 ) -> Result<(), Report> {
229 let modules = crate::parser::read_modules_from_dir(namespace, dir, &self.source_manager)?;
230 self.module_graph.add_ast_modules(modules)?;
231 Ok(())
232 }
233
234 pub fn add_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> {
249 self.module_graph
250 .add_compiled_modules(library.as_ref().module_infos())
251 .map_err(Report::from)?;
252 Ok(())
253 }
254
255 pub fn with_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
259 self.add_library(library)?;
260 Ok(self)
261 }
262
263 pub fn add_vendored_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> {
270 self.add_library(&library)?;
271 self.vendored_libraries
272 .insert(*library.as_ref().digest(), library.as_ref().clone());
273 Ok(())
274 }
275
276 pub fn with_vendored_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
280 self.add_vendored_library(library)?;
281 Ok(self)
282 }
283}
284
285impl Assembler {
288 pub fn warnings_as_errors(&self) -> bool {
290 self.warnings_as_errors
291 }
292
293 pub fn in_debug_mode(&self) -> bool {
295 self.in_debug_mode
296 }
297
298 pub fn kernel(&self) -> &Kernel {
302 self.module_graph.kernel()
303 }
304
305 pub fn source_manager(&self) -> Arc<dyn SourceManager + Send + Sync> {
307 self.source_manager.clone()
308 }
309
310 #[cfg(any(test, feature = "testing"))]
311 #[doc(hidden)]
312 pub fn module_graph(&self) -> &ModuleGraph {
313 &self.module_graph
314 }
315}
316
317impl Assembler {
320 fn assemble_common(
322 mut self,
323 modules: impl IntoIterator<Item = impl Compile>,
324 options: CompileOptions,
325 ) -> Result<Library, Report> {
326 let mut mast_forest_builder = MastForestBuilder::new(self.vendored_libraries.values())?;
327
328 let ast_module_indices = self.add_modules_with_options(modules, options)?;
329
330 let mut exports = {
331 let mut exports = BTreeMap::new();
332
333 for module_idx in ast_module_indices {
334 let ast_module = self.module_graph[module_idx].unwrap_ast().clone();
337
338 for (proc_idx, fqn) in ast_module.exported_procedures() {
339 let gid = module_idx + proc_idx;
340 self.compile_subgraph(gid, &mut mast_forest_builder)?;
341
342 let proc_root_node_id = mast_forest_builder
343 .get_procedure(gid)
344 .expect("compilation succeeded but root not found in cache")
345 .body_node_id();
346 exports.insert(fqn, proc_root_node_id);
347 }
348 }
349
350 exports
351 };
352
353 let (mast_forest, id_remappings) = mast_forest_builder.build();
354 for (_proc_name, node_id) in exports.iter_mut() {
355 if let Some(&new_node_id) = id_remappings.get(node_id) {
356 *node_id = new_node_id;
357 }
358 }
359
360 Ok(Library::new(mast_forest.into(), exports)?)
361 }
362
363 pub fn assemble_library(
369 self,
370 modules: impl IntoIterator<Item = impl Compile>,
371 ) -> Result<Library, Report> {
372 let options = CompileOptions {
373 kind: ModuleKind::Library,
374 warnings_as_errors: self.warnings_as_errors,
375 path: None,
376 };
377 self.assemble_common(modules, options)
378 }
379
380 pub fn assemble_kernel(self, module: impl Compile) -> Result<KernelLibrary, Report> {
386 let options = CompileOptions {
387 kind: ModuleKind::Kernel,
388 warnings_as_errors: self.warnings_as_errors,
389 path: Some(LibraryPath::from(LibraryNamespace::Kernel)),
390 };
391 let library = self.assemble_common([module], options)?;
392 Ok(library.try_into()?)
393 }
394
395 pub fn assemble_program(mut self, source: impl Compile) -> Result<Program, Report> {
403 let options = CompileOptions {
404 kind: ModuleKind::Executable,
405 warnings_as_errors: self.warnings_as_errors,
406 path: Some(LibraryPath::from(LibraryNamespace::Exec)),
407 };
408
409 let program = source.compile_with_options(&self.source_manager, options)?;
410 assert!(program.is_executable());
411
412 let ast_module_index = self.module_graph.add_ast_module(program)?;
414
415 let entrypoint = self.module_graph[ast_module_index]
418 .unwrap_ast()
419 .index_of(|p| p.is_main())
420 .map(|index| GlobalProcedureIndex { module: ast_module_index, index })
421 .ok_or(SemanticAnalysisError::MissingEntrypoint)?;
422
423 let mut mast_forest_builder = MastForestBuilder::new(self.vendored_libraries.values())?;
425
426 self.compile_subgraph(entrypoint, &mut mast_forest_builder)?;
427 let entry_node_id = mast_forest_builder
428 .get_procedure(entrypoint)
429 .expect("compilation succeeded but root not found in cache")
430 .body_node_id();
431
432 let (mast_forest, id_remappings) = mast_forest_builder.build();
434 let entry_node_id = *id_remappings.get(&entry_node_id).unwrap_or(&entry_node_id);
435
436 Ok(Program::with_kernel(
437 mast_forest.into(),
438 entry_node_id,
439 self.module_graph.kernel().clone(),
440 ))
441 }
442
443 fn compile_subgraph(
448 &mut self,
449 root: GlobalProcedureIndex,
450 mast_forest_builder: &mut MastForestBuilder,
451 ) -> Result<(), Report> {
452 let mut worklist: Vec<GlobalProcedureIndex> = self
453 .module_graph
454 .topological_sort_from_root(root)
455 .map_err(|cycle| {
456 let iter = cycle.into_node_ids();
457 let mut nodes = Vec::with_capacity(iter.len());
458 for node in iter {
459 let module = self.module_graph[node.module].path();
460 let proc = self.module_graph.get_procedure_unsafe(node);
461 nodes.push(format!("{}::{}", module, proc.name()));
462 }
463 AssemblyError::Cycle { nodes: nodes.into() }
464 })?
465 .into_iter()
466 .filter(|&gid| self.module_graph.get_procedure_unsafe(gid).is_ast())
467 .collect();
468
469 assert!(!worklist.is_empty());
470
471 self.process_graph_worklist(&mut worklist, mast_forest_builder)
472 }
473
474 fn process_graph_worklist(
476 &mut self,
477 worklist: &mut Vec<GlobalProcedureIndex>,
478 mast_forest_builder: &mut MastForestBuilder,
479 ) -> Result<(), Report> {
480 while let Some(procedure_gid) = worklist.pop() {
483 if let Some(proc) = mast_forest_builder.get_procedure(procedure_gid) {
485 self.module_graph.register_procedure_root(procedure_gid, proc.mast_root())?;
486 continue;
487 }
488 let module = match &self.module_graph[procedure_gid.module] {
490 WrappedModule::Ast(ast_module) => ast_module,
491 WrappedModule::Info(_) => continue,
494 };
495
496 let export = &module[procedure_gid.index];
497 match export {
498 Export::Procedure(proc) => {
499 let num_locals = proc.num_locals();
500 let name = QualifiedProcedureName {
501 span: proc.span(),
502 module: module.path().clone(),
503 name: proc.name().clone(),
504 };
505 let pctx = ProcedureContext::new(
506 procedure_gid,
507 name,
508 proc.visibility(),
509 module.is_kernel(),
510 self.source_manager.clone(),
511 )
512 .with_num_locals(num_locals)
513 .with_span(proc.span());
514
515 let procedure = self.compile_procedure(pctx, mast_forest_builder)?;
517 self.module_graph
525 .register_procedure_root(procedure_gid, procedure.mast_root())?;
526 mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
527 },
528 Export::Alias(proc_alias) => {
529 let name = QualifiedProcedureName {
530 span: proc_alias.span(),
531 module: module.path().clone(),
532 name: proc_alias.name().clone(),
533 };
534 let pctx = ProcedureContext::new(
535 procedure_gid,
536 name,
537 ast::Visibility::Public,
538 module.is_kernel(),
539 self.source_manager.clone(),
540 )
541 .with_span(proc_alias.span());
542
543 let proc_node_id = self.resolve_target(
544 InvokeKind::ProcRef,
545 &proc_alias.target().into(),
546 &pctx,
547 mast_forest_builder,
548 )?;
549 let proc_mast_root =
550 mast_forest_builder.get_mast_node(proc_node_id).unwrap().digest();
551
552 let procedure = pctx.into_procedure(proc_mast_root, proc_node_id);
553
554 self.module_graph.register_procedure_root(procedure_gid, proc_mast_root)?;
556 mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
557 },
558 }
559 }
560
561 Ok(())
562 }
563
564 fn compile_procedure(
566 &self,
567 mut proc_ctx: ProcedureContext,
568 mast_forest_builder: &mut MastForestBuilder,
569 ) -> Result<Procedure, Report> {
570 let gid = proc_ctx.id();
572
573 let num_locals = proc_ctx.num_locals();
574
575 let wrapper_proc = self.module_graph.get_procedure_unsafe(gid);
576 let proc = wrapper_proc.unwrap_ast().unwrap_procedure();
577 let proc_body_id = if num_locals > 0 {
578 let locals_frame = Felt::from(num_locals.next_multiple_of(WORD_SIZE as u16));
584 let wrapper = BodyWrapper {
585 prologue: vec![Operation::Push(locals_frame), Operation::FmpUpdate],
586 epilogue: vec![Operation::Push(-locals_frame), Operation::FmpUpdate],
587 };
588 self.compile_body(proc.iter(), &mut proc_ctx, Some(wrapper), mast_forest_builder)?
589 } else {
590 self.compile_body(proc.iter(), &mut proc_ctx, None, mast_forest_builder)?
591 };
592
593 let proc_body_node = mast_forest_builder
594 .get_mast_node(proc_body_id)
595 .expect("no MAST node for compiled procedure");
596 Ok(proc_ctx.into_procedure(proc_body_node.digest(), proc_body_id))
597 }
598
599 fn compile_body<'a, I>(
600 &self,
601 body: I,
602 proc_ctx: &mut ProcedureContext,
603 wrapper: Option<BodyWrapper>,
604 mast_forest_builder: &mut MastForestBuilder,
605 ) -> Result<MastNodeId, Report>
606 where
607 I: Iterator<Item = &'a ast::Op>,
608 {
609 use ast::Op;
610
611 let mut body_node_ids: Vec<MastNodeId> = Vec::new();
612 let mut block_builder = BasicBlockBuilder::new(wrapper, mast_forest_builder);
613
614 for op in body {
615 match op {
616 Op::Inst(inst) => {
617 if let Some(node_id) =
618 self.compile_instruction(inst, &mut block_builder, proc_ctx)?
619 {
620 if let Some(basic_block_id) = block_builder.make_basic_block()? {
621 body_node_ids.push(basic_block_id);
622 } else if let Some(decorator_ids) = block_builder.drain_decorators() {
623 block_builder
624 .mast_forest_builder_mut()
625 .append_before_enter(node_id, &decorator_ids);
626 }
627
628 body_node_ids.push(node_id);
629 }
630 },
631
632 Op::If { then_blk, else_blk, span } => {
633 if let Some(basic_block_id) = block_builder.make_basic_block()? {
634 body_node_ids.push(basic_block_id);
635 }
636
637 let then_blk = self.compile_body(
638 then_blk.iter(),
639 proc_ctx,
640 None,
641 block_builder.mast_forest_builder_mut(),
642 )?;
643 let else_blk = self.compile_body(
644 else_blk.iter(),
645 proc_ctx,
646 None,
647 block_builder.mast_forest_builder_mut(),
648 )?;
649
650 let split_node_id =
651 block_builder.mast_forest_builder_mut().ensure_split(then_blk, else_blk)?;
652 if let Some(decorator_ids) = block_builder.drain_decorators() {
653 block_builder
654 .mast_forest_builder_mut()
655 .append_before_enter(split_node_id, &decorator_ids)
656 }
657
658 if self.in_debug_mode() {
660 let location = proc_ctx.source_manager().location(*span).ok();
661 let context_name = proc_ctx.name().to_string();
662 let num_cycles = 0;
663 let op = "if.true".to_string();
664 let should_break = false;
665 let op =
666 AssemblyOp::new(location, context_name, num_cycles, op, should_break);
667 let decorator_id = block_builder
668 .mast_forest_builder_mut()
669 .ensure_decorator(Decorator::AsmOp(op))?;
670 block_builder
671 .mast_forest_builder_mut()
672 .append_before_enter(split_node_id, &[decorator_id]);
673 }
674
675 body_node_ids.push(split_node_id);
676 },
677
678 Op::Repeat { count, body, .. } => {
679 if let Some(basic_block_id) = block_builder.make_basic_block()? {
680 body_node_ids.push(basic_block_id);
681 }
682
683 let repeat_node_id = self.compile_body(
684 body.iter(),
685 proc_ctx,
686 None,
687 block_builder.mast_forest_builder_mut(),
688 )?;
689
690 if let Some(decorator_ids) = block_builder.drain_decorators() {
691 let mut first_repeat_node =
693 block_builder.mast_forest_builder_mut()[repeat_node_id].clone();
694 first_repeat_node.append_before_enter(&decorator_ids);
695 let first_repeat_node_id = block_builder
696 .mast_forest_builder_mut()
697 .ensure_node(first_repeat_node)?;
698
699 body_node_ids.push(first_repeat_node_id);
700 for _ in 0..(*count - 1) {
701 body_node_ids.push(repeat_node_id);
702 }
703 } else {
704 for _ in 0..*count {
705 body_node_ids.push(repeat_node_id);
706 }
707 }
708 },
709
710 Op::While { body, span } => {
711 if let Some(basic_block_id) = block_builder.make_basic_block()? {
712 body_node_ids.push(basic_block_id);
713 }
714
715 let loop_node_id = {
716 let loop_body_node_id = self.compile_body(
717 body.iter(),
718 proc_ctx,
719 None,
720 block_builder.mast_forest_builder_mut(),
721 )?;
722 block_builder.mast_forest_builder_mut().ensure_loop(loop_body_node_id)?
723 };
724 if let Some(decorator_ids) = block_builder.drain_decorators() {
725 block_builder
726 .mast_forest_builder_mut()
727 .append_before_enter(loop_node_id, &decorator_ids)
728 }
729
730 if self.in_debug_mode() {
732 let location = proc_ctx.source_manager().location(*span).ok();
733 let context_name = proc_ctx.name().to_string();
734 let num_cycles = 0;
735 let op = "while.true".to_string();
736 let should_break = false;
737 let op =
738 AssemblyOp::new(location, context_name, num_cycles, op, should_break);
739 let decorator_id = block_builder
740 .mast_forest_builder_mut()
741 .ensure_decorator(Decorator::AsmOp(op))?;
742 block_builder
743 .mast_forest_builder_mut()
744 .append_before_enter(loop_node_id, &[decorator_id]);
745 }
746
747 body_node_ids.push(loop_node_id);
748 },
749 }
750 }
751
752 let maybe_post_decorators: Option<Vec<DecoratorId>> =
753 match block_builder.try_into_basic_block()? {
754 BasicBlockOrDecorators::BasicBlock(basic_block_id) => {
755 body_node_ids.push(basic_block_id);
756 None
757 },
758 BasicBlockOrDecorators::Decorators(decorator_ids) => {
759 Some(decorator_ids)
761 },
762 BasicBlockOrDecorators::Nothing => None,
763 };
764
765 let procedure_body_id = if body_node_ids.is_empty() {
766 if maybe_post_decorators.is_some() {
770 return Err(AssemblyError::EmptyProcedureBodyWithDecorators {
771 span: proc_ctx.span(),
772 source_file: proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
773 })?;
774 }
775
776 mast_forest_builder.ensure_block(vec![Operation::Noop], None)?
777 } else {
778 mast_forest_builder.join_nodes(body_node_ids)?
779 };
780
781 if let Some(post_decorator_ids) = maybe_post_decorators {
783 mast_forest_builder.append_after_exit(procedure_body_id, &post_decorator_ids);
784 }
785
786 Ok(procedure_body_id)
787 }
788
789 pub(super) fn resolve_target(
794 &self,
795 kind: InvokeKind,
796 target: &InvocationTarget,
797 proc_ctx: &ProcedureContext,
798 mast_forest_builder: &mut MastForestBuilder,
799 ) -> Result<MastNodeId, AssemblyError> {
800 let caller = CallerInfo {
801 span: target.span(),
802 module: proc_ctx.id().module,
803 kind,
804 };
805 let resolved = self.module_graph.resolve_target(&caller, target)?;
806 match resolved {
807 ResolvedTarget::Phantom(mast_root) => self.ensure_valid_procedure_mast_root(
808 kind,
809 target.span(),
810 mast_root,
811 mast_forest_builder,
812 ),
813 ResolvedTarget::Exact { gid } | ResolvedTarget::Resolved { gid, .. } => {
814 match mast_forest_builder.get_procedure(gid) {
815 Some(proc) => Ok(proc.body_node_id()),
816 None => match self.module_graph.get_procedure_unsafe(gid) {
819 ProcedureWrapper::Info(p) => self.ensure_valid_procedure_mast_root(
820 kind,
821 target.span(),
822 p.digest,
823 mast_forest_builder,
824 ),
825 ProcedureWrapper::Ast(_) => panic!(
826 "AST procedure {gid:?} exits in the module graph but not in the MastForestBuilder"
827 ),
828 },
829 }
830 },
831 }
832 }
833
834 fn ensure_valid_procedure_mast_root(
839 &self,
840 kind: InvokeKind,
841 span: SourceSpan,
842 mast_root: RpoDigest,
843 mast_forest_builder: &mut MastForestBuilder,
844 ) -> Result<MastNodeId, AssemblyError> {
845 let current_source_file = self.source_manager.get(span.source_id()).ok();
847
848 match mast_forest_builder.find_procedure_by_mast_root(&mast_root) {
850 Some(proc) if matches!(kind, InvokeKind::SysCall) => {
851 if !proc.visibility().is_syscall() {
857 return Err(AssemblyError::InvalidSysCallTarget {
858 span,
859 source_file: current_source_file,
860 callee: proc.fully_qualified_name().clone().into(),
861 });
862 }
863 let maybe_kernel_path = proc.path();
864 self.module_graph
865 .find_module(maybe_kernel_path)
866 .ok_or_else(|| AssemblyError::InvalidSysCallTarget {
867 span,
868 source_file: current_source_file.clone(),
869 callee: proc.fully_qualified_name().clone().into(),
870 })
871 .and_then(|module| {
872 if module.unwrap_ast().is_kernel() {
876 Ok(())
877 } else {
878 Err(AssemblyError::InvalidSysCallTarget {
879 span,
880 source_file: current_source_file.clone(),
881 callee: proc.fully_qualified_name().clone().into(),
882 })
883 }
884 })?;
885 },
886 Some(_) | None => (),
887 }
888
889 mast_forest_builder.vendor_or_ensure_external(mast_root)
890 }
891}
892
893struct BodyWrapper {
899 prologue: Vec<Operation>,
900 epilogue: Vec<Operation>,
901}