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 crypto::hash::RpoDigest,
8 debuginfo::SourceSpan,
9 mast::{DecoratorId, MastNodeId},
10 DecoratorList, Felt, Kernel, Operation, Program, WORD_SIZE,
11};
12
13use crate::{
14 ast::{self, Export, InvocationTarget, InvokeKind, ModuleKind, QualifiedProcedureName},
15 diagnostics::Report,
16 library::{KernelLibrary, Library},
17 sema::SemanticAnalysisError,
18 AssemblyError, Compile, CompileOptions, LibraryNamespace, LibraryPath, SourceManager, Spanned,
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}
75
76impl Default for Assembler {
77 fn default() -> Self {
78 let source_manager = Arc::new(crate::DefaultSourceManager::default());
79 let module_graph = ModuleGraph::new(source_manager.clone());
80 Self {
81 source_manager,
82 module_graph,
83 warnings_as_errors: false,
84 in_debug_mode: false,
85 }
86 }
87}
88
89impl Assembler {
92 pub fn new(source_manager: Arc<dyn SourceManager>) -> Self {
94 let module_graph = ModuleGraph::new(source_manager.clone());
95 Self {
96 source_manager,
97 module_graph,
98 warnings_as_errors: false,
99 in_debug_mode: false,
100 }
101 }
102
103 pub fn with_kernel(source_manager: Arc<dyn SourceManager>, kernel_lib: KernelLibrary) -> Self {
105 let (kernel, kernel_module, _) = kernel_lib.into_parts();
106 let module_graph = ModuleGraph::with_kernel(source_manager.clone(), kernel, kernel_module);
107 Self {
108 source_manager,
109 module_graph,
110 ..Default::default()
111 }
112 }
113
114 pub fn with_warnings_as_errors(mut self, yes: bool) -> Self {
118 self.warnings_as_errors = yes;
119 self
120 }
121
122 pub fn with_debug_mode(mut self, yes: bool) -> Self {
124 self.in_debug_mode = yes;
125 self
126 }
127
128 pub fn set_debug_mode(&mut self, yes: bool) {
130 self.in_debug_mode = yes;
131 }
132
133 #[inline]
137 pub fn with_module(mut self, module: impl Compile) -> Result<Self, Report> {
138 self.add_module(module)?;
139
140 Ok(self)
141 }
142
143 #[inline]
147 pub fn with_module_and_options(
148 mut self,
149 module: impl Compile,
150 options: CompileOptions,
151 ) -> Result<Self, Report> {
152 self.add_module_with_options(module, options)?;
153
154 Ok(self)
155 }
156
157 #[inline]
161 pub fn add_module(&mut self, module: impl Compile) -> Result<ModuleIndex, Report> {
162 self.add_module_with_options(module, CompileOptions::for_library())
163 }
164
165 pub fn add_module_with_options(
169 &mut self,
170 module: impl Compile,
171 options: CompileOptions,
172 ) -> Result<ModuleIndex, Report> {
173 let ids = self.add_modules_with_options(vec![module], options)?;
174 Ok(ids[0])
175 }
176
177 pub fn add_modules_with_options(
178 &mut self,
179 modules: impl IntoIterator<Item = impl Compile>,
180 options: CompileOptions,
181 ) -> Result<Vec<ModuleIndex>, Report> {
182 let kind = options.kind;
183 if kind == ModuleKind::Executable {
184 return Err(Report::msg("Executables are not supported by `add_module_with_options`"));
185 }
186
187 let modules = modules
188 .into_iter()
189 .map(|module| {
190 let module = module.compile_with_options(&self.source_manager, options.clone())?;
191 assert_eq!(
192 module.kind(),
193 kind,
194 "expected module kind to match compilation options"
195 );
196 Ok(module)
197 })
198 .collect::<Result<Vec<_>, Report>>()?;
199 let ids = self.module_graph.add_ast_modules(modules.into_iter())?;
200 Ok(ids)
201 }
202 #[cfg(feature = "std")]
214 pub fn add_modules_from_dir(
215 &mut self,
216 namespace: crate::LibraryNamespace,
217 dir: &std::path::Path,
218 ) -> Result<(), Report> {
219 let modules = crate::parser::read_modules_from_dir(namespace, dir, &self.source_manager)?;
220 self.module_graph.add_ast_modules(modules)?;
221 Ok(())
222 }
223
224 pub fn add_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> {
241 self.module_graph
242 .add_compiled_modules(library.as_ref().module_infos())
243 .map_err(Report::from)?;
244 Ok(())
245 }
246
247 pub fn with_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
251 self.add_library(library)?;
252 Ok(self)
253 }
254}
255
256impl Assembler {
259 pub fn warnings_as_errors(&self) -> bool {
261 self.warnings_as_errors
262 }
263
264 pub fn in_debug_mode(&self) -> bool {
266 self.in_debug_mode
267 }
268
269 pub fn kernel(&self) -> &Kernel {
273 self.module_graph.kernel()
274 }
275
276 pub fn source_manager(&self) -> Arc<dyn SourceManager> {
278 self.source_manager.clone()
279 }
280
281 #[cfg(any(test, feature = "testing"))]
282 #[doc(hidden)]
283 pub fn module_graph(&self) -> &ModuleGraph {
284 &self.module_graph
285 }
286}
287
288impl Assembler {
291 pub fn assemble_common(
297 mut self,
298 modules: impl IntoIterator<Item = impl Compile>,
299 options: CompileOptions,
300 ) -> Result<Library, Report> {
301 let ast_module_indices = self.add_modules_with_options(modules, options)?;
302
303 let mut mast_forest_builder = MastForestBuilder::default();
304
305 let mut exports = {
306 let mut exports = BTreeMap::new();
307
308 for module_idx in ast_module_indices {
309 let ast_module = self.module_graph[module_idx].unwrap_ast().clone();
312
313 for (proc_idx, fqn) in ast_module.exported_procedures() {
314 let gid = module_idx + proc_idx;
315 self.compile_subgraph(gid, &mut mast_forest_builder)?;
316
317 let proc_root_node_id = mast_forest_builder
318 .get_procedure(gid)
319 .expect("compilation succeeded but root not found in cache")
320 .body_node_id();
321 exports.insert(fqn, proc_root_node_id);
322 }
323 }
324
325 exports
326 };
327
328 let (mast_forest, id_remappings) = mast_forest_builder.build();
329 if let Some(id_remappings) = id_remappings {
330 for (_proc_name, node_id) in exports.iter_mut() {
331 if let Some(&new_node_id) = id_remappings.get(node_id) {
332 *node_id = new_node_id;
333 }
334 }
335 }
336
337 Ok(Library::new(mast_forest.into(), exports)?)
338 }
339
340 pub fn assemble_library(
341 self,
342 modules: impl IntoIterator<Item = impl Compile>,
343 ) -> Result<Library, Report> {
344 let options = CompileOptions {
345 kind: ModuleKind::Library,
346 warnings_as_errors: self.warnings_as_errors,
347 path: None,
348 };
349 self.assemble_common(modules, options)
350 }
351
352 pub fn assemble_kernel(self, module: impl Compile) -> Result<KernelLibrary, Report> {
358 let options = CompileOptions {
359 kind: ModuleKind::Kernel,
360 warnings_as_errors: self.warnings_as_errors,
361 path: Some(LibraryPath::from(LibraryNamespace::Kernel)),
362 };
363 let library = self.assemble_common([module], options)?;
364 Ok(library.try_into()?)
365 }
366
367 pub fn assemble_program(mut self, source: impl Compile) -> Result<Program, Report> {
375 let options = CompileOptions {
376 kind: ModuleKind::Executable,
377 warnings_as_errors: self.warnings_as_errors,
378 path: Some(LibraryPath::from(LibraryNamespace::Exec)),
379 };
380
381 let program = source.compile_with_options(&self.source_manager, options)?;
382 assert!(program.is_executable());
383
384 let ast_module_index = self.module_graph.add_ast_module(program)?;
386
387 let entrypoint = self.module_graph[ast_module_index]
390 .unwrap_ast()
391 .index_of(|p| p.is_main())
392 .map(|index| GlobalProcedureIndex { module: ast_module_index, index })
393 .ok_or(SemanticAnalysisError::MissingEntrypoint)?;
394
395 let mut mast_forest_builder = MastForestBuilder::default();
397 self.compile_subgraph(entrypoint, &mut mast_forest_builder)?;
398 let entry_node_id = mast_forest_builder
399 .get_procedure(entrypoint)
400 .expect("compilation succeeded but root not found in cache")
401 .body_node_id();
402
403 let (mast_forest, id_remappings) = mast_forest_builder.build();
405 let entry_node_id = id_remappings
406 .map(|id_remappings| id_remappings[&entry_node_id])
407 .unwrap_or(entry_node_id);
408
409 Ok(Program::with_kernel(
410 mast_forest.into(),
411 entry_node_id,
412 self.module_graph.kernel().clone(),
413 ))
414 }
415
416 fn compile_subgraph(
421 &mut self,
422 root: GlobalProcedureIndex,
423 mast_forest_builder: &mut MastForestBuilder,
424 ) -> Result<(), Report> {
425 let mut worklist: Vec<GlobalProcedureIndex> = self
426 .module_graph
427 .topological_sort_from_root(root)
428 .map_err(|cycle| {
429 let iter = cycle.into_node_ids();
430 let mut nodes = Vec::with_capacity(iter.len());
431 for node in iter {
432 let module = self.module_graph[node.module].path();
433 let proc = self.module_graph.get_procedure_unsafe(node);
434 nodes.push(format!("{}::{}", module, proc.name()));
435 }
436 AssemblyError::Cycle { nodes }
437 })?
438 .into_iter()
439 .filter(|&gid| self.module_graph.get_procedure_unsafe(gid).is_ast())
440 .collect();
441
442 assert!(!worklist.is_empty());
443
444 self.process_graph_worklist(&mut worklist, mast_forest_builder)
445 }
446
447 fn process_graph_worklist(
449 &mut self,
450 worklist: &mut Vec<GlobalProcedureIndex>,
451 mast_forest_builder: &mut MastForestBuilder,
452 ) -> Result<(), Report> {
453 while let Some(procedure_gid) = worklist.pop() {
456 if let Some(proc) = mast_forest_builder.get_procedure(procedure_gid) {
458 self.module_graph.register_procedure_root(procedure_gid, proc.mast_root())?;
459 continue;
460 }
461 let module = match &self.module_graph[procedure_gid.module] {
463 WrappedModule::Ast(ast_module) => ast_module,
464 WrappedModule::Info(_) => continue,
467 };
468
469 let export = &module[procedure_gid.index];
470 match export {
471 Export::Procedure(proc) => {
472 let num_locals = proc.num_locals();
473 let name = QualifiedProcedureName {
474 span: proc.span(),
475 module: module.path().clone(),
476 name: proc.name().clone(),
477 };
478 let pctx = ProcedureContext::new(
479 procedure_gid,
480 name,
481 proc.visibility(),
482 module.is_kernel(),
483 self.source_manager.clone(),
484 )
485 .with_num_locals(num_locals)
486 .with_span(proc.span());
487
488 let procedure = self.compile_procedure(pctx, mast_forest_builder)?;
490 self.module_graph
498 .register_procedure_root(procedure_gid, procedure.mast_root())?;
499 mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
500 },
501 Export::Alias(proc_alias) => {
502 let name = QualifiedProcedureName {
503 span: proc_alias.span(),
504 module: module.path().clone(),
505 name: proc_alias.name().clone(),
506 };
507 let pctx = ProcedureContext::new(
508 procedure_gid,
509 name,
510 ast::Visibility::Public,
511 module.is_kernel(),
512 self.source_manager.clone(),
513 )
514 .with_span(proc_alias.span());
515
516 let proc_node_id = self.resolve_target(
517 InvokeKind::ProcRef,
518 &proc_alias.target().into(),
519 &pctx,
520 mast_forest_builder,
521 )?;
522 let proc_mast_root =
523 mast_forest_builder.get_mast_node(proc_node_id).unwrap().digest();
524
525 let procedure = pctx.into_procedure(proc_mast_root, proc_node_id);
526
527 self.module_graph.register_procedure_root(procedure_gid, proc_mast_root)?;
529 mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
530 },
531 }
532 }
533
534 Ok(())
535 }
536
537 fn compile_procedure(
539 &self,
540 mut proc_ctx: ProcedureContext,
541 mast_forest_builder: &mut MastForestBuilder,
542 ) -> Result<Procedure, Report> {
543 let gid = proc_ctx.id();
545
546 let num_locals = proc_ctx.num_locals();
547
548 let wrapper_proc = self.module_graph.get_procedure_unsafe(gid);
549 let proc = wrapper_proc.unwrap_ast().unwrap_procedure();
550 let proc_body_id = if num_locals > 0 {
551 let locals_frame = Felt::from(num_locals.next_multiple_of(WORD_SIZE as u16));
557 let wrapper = BodyWrapper {
558 prologue: vec![Operation::Push(locals_frame), Operation::FmpUpdate],
559 epilogue: vec![Operation::Push(-locals_frame), Operation::FmpUpdate],
560 };
561 self.compile_body(proc.iter(), &mut proc_ctx, Some(wrapper), mast_forest_builder)?
562 } else {
563 self.compile_body(proc.iter(), &mut proc_ctx, None, mast_forest_builder)?
564 };
565
566 let proc_body_node = mast_forest_builder
567 .get_mast_node(proc_body_id)
568 .expect("no MAST node for compiled procedure");
569 Ok(proc_ctx.into_procedure(proc_body_node.digest(), proc_body_id))
570 }
571
572 fn compile_body<'a, I>(
573 &self,
574 body: I,
575 proc_ctx: &mut ProcedureContext,
576 wrapper: Option<BodyWrapper>,
577 mast_forest_builder: &mut MastForestBuilder,
578 ) -> Result<MastNodeId, Report>
579 where
580 I: Iterator<Item = &'a ast::Op>,
581 {
582 use ast::Op;
583
584 let mut body_node_ids: Vec<MastNodeId> = Vec::new();
585 let mut block_builder = BasicBlockBuilder::new(wrapper, mast_forest_builder);
586
587 for op in body {
588 match op {
589 Op::Inst(inst) => {
590 if let Some(node_id) =
591 self.compile_instruction(inst, &mut block_builder, proc_ctx)?
592 {
593 if let Some(basic_block_id) = block_builder.make_basic_block()? {
594 body_node_ids.push(basic_block_id);
595 } else if let Some(decorator_ids) = block_builder.drain_decorators() {
596 block_builder
597 .mast_forest_builder_mut()
598 .set_before_enter(node_id, decorator_ids);
599 }
600
601 body_node_ids.push(node_id);
602 }
603 },
604
605 Op::If { then_blk, else_blk, .. } => {
606 if let Some(basic_block_id) = block_builder.make_basic_block()? {
607 body_node_ids.push(basic_block_id);
608 }
609
610 let then_blk = self.compile_body(
611 then_blk.iter(),
612 proc_ctx,
613 None,
614 block_builder.mast_forest_builder_mut(),
615 )?;
616 let else_blk = self.compile_body(
617 else_blk.iter(),
618 proc_ctx,
619 None,
620 block_builder.mast_forest_builder_mut(),
621 )?;
622
623 let split_node_id =
624 block_builder.mast_forest_builder_mut().ensure_split(then_blk, else_blk)?;
625 if let Some(decorator_ids) = block_builder.drain_decorators() {
626 block_builder
627 .mast_forest_builder_mut()
628 .set_before_enter(split_node_id, decorator_ids)
629 }
630
631 body_node_ids.push(split_node_id);
632 },
633
634 Op::Repeat { count, body, .. } => {
635 if let Some(basic_block_id) = block_builder.make_basic_block()? {
636 body_node_ids.push(basic_block_id);
637 }
638
639 let repeat_node_id = self.compile_body(
640 body.iter(),
641 proc_ctx,
642 None,
643 block_builder.mast_forest_builder_mut(),
644 )?;
645
646 if let Some(decorator_ids) = block_builder.drain_decorators() {
647 let mut first_repeat_node =
649 block_builder.mast_forest_builder_mut()[repeat_node_id].clone();
650 first_repeat_node.set_before_enter(decorator_ids);
651 let first_repeat_node_id = block_builder
652 .mast_forest_builder_mut()
653 .ensure_node(first_repeat_node)?;
654
655 body_node_ids.push(first_repeat_node_id);
656 for _ in 0..(*count - 1) {
657 body_node_ids.push(repeat_node_id);
658 }
659 } else {
660 for _ in 0..*count {
661 body_node_ids.push(repeat_node_id);
662 }
663 }
664 },
665
666 Op::While { body, .. } => {
667 if let Some(basic_block_id) = block_builder.make_basic_block()? {
668 body_node_ids.push(basic_block_id);
669 }
670
671 let loop_node_id = {
672 let loop_body_node_id = self.compile_body(
673 body.iter(),
674 proc_ctx,
675 None,
676 block_builder.mast_forest_builder_mut(),
677 )?;
678 block_builder.mast_forest_builder_mut().ensure_loop(loop_body_node_id)?
679 };
680 if let Some(decorator_ids) = block_builder.drain_decorators() {
681 block_builder
682 .mast_forest_builder_mut()
683 .set_before_enter(loop_node_id, decorator_ids)
684 }
685
686 body_node_ids.push(loop_node_id);
687 },
688 }
689 }
690
691 let maybe_post_decorators: Option<Vec<DecoratorId>> =
692 match block_builder.try_into_basic_block()? {
693 BasicBlockOrDecorators::BasicBlock(basic_block_id) => {
694 body_node_ids.push(basic_block_id);
695 None
696 },
697 BasicBlockOrDecorators::Decorators(decorator_ids) => {
698 Some(decorator_ids)
700 },
701 BasicBlockOrDecorators::Nothing => None,
702 };
703
704 let procedure_body_id = if body_node_ids.is_empty() {
705 if maybe_post_decorators.is_some() {
709 return Err(AssemblyError::EmptyProcedureBodyWithDecorators {
710 span: proc_ctx.span(),
711 source_file: proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
712 })?;
713 }
714
715 mast_forest_builder.ensure_block(vec![Operation::Noop], None)?
716 } else {
717 mast_forest_builder.join_nodes(body_node_ids)?
718 };
719
720 if let Some(post_decorator_ids) = maybe_post_decorators {
722 mast_forest_builder.set_after_exit(procedure_body_id, post_decorator_ids);
723 }
724
725 Ok(procedure_body_id)
726 }
727
728 pub(super) fn resolve_target(
733 &self,
734 kind: InvokeKind,
735 target: &InvocationTarget,
736 proc_ctx: &ProcedureContext,
737 mast_forest_builder: &mut MastForestBuilder,
738 ) -> Result<MastNodeId, AssemblyError> {
739 let caller = CallerInfo {
740 span: target.span(),
741 module: proc_ctx.id().module,
742 kind,
743 };
744 let resolved = self.module_graph.resolve_target(&caller, target)?;
745 match resolved {
746 ResolvedTarget::Phantom(mast_root) => self.ensure_valid_procedure_mast_root(
747 kind,
748 target.span(),
749 mast_root,
750 mast_forest_builder,
751 ),
752 ResolvedTarget::Exact { gid } | ResolvedTarget::Resolved { gid, .. } => {
753 match mast_forest_builder.get_procedure(gid) {
754 Some(proc) => Ok(proc.body_node_id()),
755 None => match self.module_graph.get_procedure_unsafe(gid) {
758 ProcedureWrapper::Info(p) => self.ensure_valid_procedure_mast_root(
759 kind,
760 target.span(),
761 p.digest,
762 mast_forest_builder,
763 )
764 ,
765 ProcedureWrapper::Ast(_) => panic!("AST procedure {gid:?} exits in the module graph but not in the MastForestBuilder"),
766 },
767 }
768 },
769 }
770 }
771
772 fn ensure_valid_procedure_mast_root(
775 &self,
776 kind: InvokeKind,
777 span: SourceSpan,
778 mast_root: RpoDigest,
779 mast_forest_builder: &mut MastForestBuilder,
780 ) -> Result<MastNodeId, AssemblyError> {
781 let current_source_file = self.source_manager.get(span.source_id()).ok();
783
784 match mast_forest_builder.find_procedure_by_mast_root(&mast_root) {
786 Some(proc) if matches!(kind, InvokeKind::SysCall) => {
787 if !proc.visibility().is_syscall() {
793 return Err(AssemblyError::InvalidSysCallTarget {
794 span,
795 source_file: current_source_file,
796 callee: proc.fully_qualified_name().clone(),
797 });
798 }
799 let maybe_kernel_path = proc.path();
800 self.module_graph
801 .find_module(maybe_kernel_path)
802 .ok_or_else(|| AssemblyError::InvalidSysCallTarget {
803 span,
804 source_file: current_source_file.clone(),
805 callee: proc.fully_qualified_name().clone(),
806 })
807 .and_then(|module| {
808 if module.unwrap_ast().is_kernel() {
812 Ok(())
813 } else {
814 Err(AssemblyError::InvalidSysCallTarget {
815 span,
816 source_file: current_source_file.clone(),
817 callee: proc.fully_qualified_name().clone(),
818 })
819 }
820 })?;
821 },
822 Some(_) | None => (),
823 }
824
825 mast_forest_builder.ensure_external(mast_root)
826 }
827}
828
829struct BodyWrapper {
835 prologue: Vec<Operation>,
836 epilogue: Vec<Operation>,
837}