miden_assembly/assembler/
mod.rs

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// ASSEMBLER
44// ================================================================================================
45
46/// The [Assembler] is the primary interface for compiling Miden Assembly to the Merkelized
47/// Abstract Syntax Tree (MAST).
48///
49/// # Usage
50///
51/// Depending on your needs, there are multiple ways of using the assembler, and whether or not you
52/// want to provide a custom kernel.
53///
54/// <div class="warning">
55/// Programs compiled with an empty kernel cannot use the `syscall` instruction.
56/// </div>
57///
58/// * If you have a single executable module you want to compile, just call
59///   [Assembler::assemble_program].
60/// * If you want to link your executable to a few other modules that implement supporting
61///   procedures, build the assembler with them first, using the various builder methods on
62///   [Assembler], e.g. [Assembler::with_module], [Assembler::with_library], etc. Then, call
63///   [Assembler::assemble_program] to get your compiled program.
64#[derive(Clone)]
65pub struct Assembler {
66    /// The source manager to use for compilation and source location information
67    source_manager: Arc<dyn SourceManager>,
68    /// The global [ModuleGraph] for this assembler.
69    module_graph: ModuleGraph,
70    /// Whether to treat warning diagnostics as errors
71    warnings_as_errors: bool,
72    /// Whether the assembler enables extra debugging information.
73    in_debug_mode: bool,
74    /// Collects libraries that can be used during assembly to vendor procedures.
75    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
92// ------------------------------------------------------------------------------------------------
93/// Constructors
94impl Assembler {
95    /// Start building an [Assembler]
96    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    /// Start building an [`Assembler`] with a kernel defined by the provided [KernelLibrary].
108    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    /// Sets the default behavior of this assembler with regard to warning diagnostics.
119    ///
120    /// When true, any warning diagnostics that are emitted will be promoted to errors.
121    pub fn with_warnings_as_errors(mut self, yes: bool) -> Self {
122        self.warnings_as_errors = yes;
123        self
124    }
125
126    /// Puts the assembler into the debug mode.
127    pub fn with_debug_mode(mut self, yes: bool) -> Self {
128        self.in_debug_mode = yes;
129        self
130    }
131
132    /// Sets the debug mode flag of the assembler
133    pub fn set_debug_mode(&mut self, yes: bool) {
134        self.in_debug_mode = yes;
135    }
136
137    /// Adds `module` to the module graph of the assembler.
138    ///
139    /// The given module must be a library module, or an error will be returned.
140    #[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    /// Adds `module` to the module graph of the assembler with the given options.
148    ///
149    /// The given module must be a library module, or an error will be returned.
150    #[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    /// Adds `module` to the module graph of the assembler.
162    ///
163    /// The given module must be a library module, or an error will be returned.
164    #[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    /// Adds `module` to the module graph of the assembler, using the provided options.
170    ///
171    /// The given module must be a library or kernel module, or an error will be returned.
172    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    /// Adds a set of modules to the module graph of the assembler, using the provided options.
182    ///
183    /// The modules must all be library or kernel modules, or an error will be returned.
184    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    /// Adds all modules (defined by ".masm" files) from the specified directory to the module
210    /// of this assembler graph.
211    ///
212    /// The modules will be added under the specified namespace, but otherwise preserving the
213    /// structure of the directory. Any module named `mod.masm` will be added using parent
214    /// directory path For example, if `namespace` = "ns", modules from the ~/masm directory
215    /// will be added as follows:
216    ///
217    /// - ~/masm/foo.masm        -> "ns::foo"
218    /// - ~/masm/bar/mod.masm    -> "ns::bar"
219    /// - ~/masm/bar/baz.masm    -> "ns::bar::baz"
220    #[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    /// Adds the compiled library to provide modules for the compilation.
232    ///
233    /// All calls to the library's procedures will be compiled down to a
234    /// [`vm_core::mast::ExternalNode`] (i.e. a reference to the procedure's MAST root).
235    /// The library's source code is expected to be loaded in the processor at execution time.
236    /// This means that when executing a program compiled against a library, the processor will not
237    /// be able to differentiate procedures with the same MAST root but different decorators.
238    ///
239    /// Hence, it is not recommended to export two procedures that have the same MAST root (i.e. are
240    /// identical except for their decorators). Note however that we don't expect this scenario to
241    /// be frequent in practice. For example, this could occur when APIs are being renamed and/or
242    /// moved between modules, and for some deprecation period, the same is exported under both its
243    /// old and new paths. Or possibly with common small functions that are implemented by the main
244    /// program and one of its dependencies.
245    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    /// Adds the compiled library to provide modules for the compilation.
253    ///
254    /// See [`Self::add_library`] for more detailed information.
255    pub fn with_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
256        self.add_library(library)?;
257        Ok(self)
258    }
259
260    /// Adds a compiled library from which procedures will be vendored into the assembled code.
261    ///
262    /// Vendoring in this context means that when a procedure from this library is invoked from the
263    /// assembled code, the entire procedure MAST will be copied into the assembled code. Thus,
264    /// when the resulting code is executed on the VM, the vendored library does not need to be
265    /// provided to the VM to resolve external calls.
266    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    /// Adds a compiled library from which procedures will be vendored into the assembled code.
274    ///
275    /// See [`Self::add_vendored_library`]
276    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
282// ------------------------------------------------------------------------------------------------
283/// Public Accessors
284impl Assembler {
285    /// Returns true if this assembler promotes warning diagnostics as errors by default.
286    pub fn warnings_as_errors(&self) -> bool {
287        self.warnings_as_errors
288    }
289
290    /// Returns true if this assembler was instantiated in debug mode.
291    pub fn in_debug_mode(&self) -> bool {
292        self.in_debug_mode
293    }
294
295    /// Returns a reference to the kernel for this assembler.
296    ///
297    /// If the assembler was instantiated without a kernel, the internal kernel will be empty.
298    pub fn kernel(&self) -> &Kernel {
299        self.module_graph.kernel()
300    }
301
302    /// Returns a link to the source manager used by this assembler.
303    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
314// ------------------------------------------------------------------------------------------------
315/// Compilation/Assembly
316impl Assembler {
317    /// Shared code used by both [Assembler::assemble_library()] and [Assembler::assemble_kernel()].
318    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                // Note: it is safe to use `unwrap_ast()` here, since all of the modules contained
332                // in `ast_module_indices` are in AST form by definition.
333                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    /// Assembles a set of modules into a [Library].
361    ///
362    /// # Errors
363    ///
364    /// Returns an error if parsing or compilation of the specified modules fails.
365    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    /// Assembles the provided module into a [KernelLibrary] intended to be used as a Kernel.
378    ///
379    /// # Errors
380    ///
381    /// Returns an error if parsing or compilation of the specified modules fails.
382    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    /// Compiles the provided module into a [`Program`]. The resulting program can be executed on
393    /// Miden VM.
394    ///
395    /// # Errors
396    ///
397    /// Returns an error if parsing or compilation of the specified program fails, or if the source
398    /// doesn't have an entrypoint.
399    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        // Recompute graph with executable module, and start compiling
410        let ast_module_index = self.module_graph.add_ast_module(program)?;
411
412        // Find the executable entrypoint Note: it is safe to use `unwrap_ast()` here, since this is
413        // the module we just added, which is in AST representation.
414        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        // Compile the module graph rooted at the entrypoint
421        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        // in case the node IDs changed, update the entrypoint ID to the new value
430        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    /// Compile the uncompiled procedure in the module graph which are members of the subgraph
441    /// rooted at `root`, placing them in the MAST forest builder once compiled.
442    ///
443    /// Returns an error if any of the provided Miden Assembly is invalid.
444    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    /// Compiles all procedures in the `worklist`.
472    fn process_graph_worklist(
473        &mut self,
474        worklist: &mut Vec<GlobalProcedureIndex>,
475        mast_forest_builder: &mut MastForestBuilder,
476    ) -> Result<(), Report> {
477        // Process the topological ordering in reverse order (bottom-up), so that
478        // each procedure is compiled with all of its dependencies fully compiled
479        while let Some(procedure_gid) = worklist.pop() {
480            // If we have already compiled this procedure, do not recompile
481            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            // Fetch procedure metadata from the graph
486            let module = match &self.module_graph[procedure_gid.module] {
487                WrappedModule::Ast(ast_module) => ast_module,
488                // Note: if the containing module is in `Info` representation, there is nothing to
489                // compile.
490                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                    // Compile this procedure
513                    let procedure = self.compile_procedure(pctx, mast_forest_builder)?;
514                    // TODO: if a re-exported procedure with the same MAST root had been previously
515                    // added to the builder, this will result in unreachable nodes added to the
516                    // MAST forest. This is because while we won't insert a duplicate node for the
517                    // procedure body node itself, all nodes that make up the procedure body would
518                    // be added to the forest.
519
520                    // Cache the compiled procedure
521                    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                    // Make the MAST root available to all dependents
552                    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    /// Compiles a single Miden Assembly procedure to its MAST representation.
562    fn compile_procedure(
563        &self,
564        mut proc_ctx: ProcedureContext,
565        mast_forest_builder: &mut MastForestBuilder,
566    ) -> Result<Procedure, Report> {
567        // Make sure the current procedure context is available during codegen
568        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            // For procedures with locals, we need to update fmp register before and after the
576            // procedure body is executed. Specifically:
577            // - to allocate procedure locals we need to increment fmp by the number of locals
578            //   (rounded up to the word size), and
579            // - to deallocate procedure locals we need to decrement it by the same amount.
580            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                    // Add an assembly operation decorator to the if node in debug mode.
656                    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                        // Attach the decorators before the first instance of the repeated node
689                        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                    // Add an assembly operation decorator to the loop node in debug mode.
728                    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                    // the procedure body ends with a list of decorators
757                    Some(decorator_ids)
758                },
759                BasicBlockOrDecorators::Nothing => None,
760            };
761
762        let procedure_body_id = if body_node_ids.is_empty() {
763            // We cannot allow only decorators in a procedure body, since decorators don't change
764            // the MAST digest of a node. Hence, two empty procedures with different decorators
765            // would look the same to the `MastForestBuilder`.
766            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        // Make sure that any post decorators are added at the end of the procedure body
779        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    /// Resolves the specified target to the corresponding procedure root [`MastNodeId`].
787    ///
788    /// If no [`MastNodeId`] exists for that procedure root, we wrap the root in an
789    /// [`crate::mast::ExternalNode`], and return the resulting [`MastNodeId`].
790    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                    // We didn't find the procedure in our current MAST forest. We still need to
814                    // check if it exists in one of a library dependency.
815                    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    /// Verifies the validity of the MAST root as a procedure root hash, and adds it to the forest.
832    ///
833    /// If the root is present in the vendored MAST, its subtree is copied. Otherwise an
834    /// external node is added to the forest.
835    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        // Get the procedure from the assembler
843        let current_source_file = self.source_manager.get(span.source_id()).ok();
844
845        // If the procedure is cached and is a system call, ensure that the call is valid.
846        match mast_forest_builder.find_procedure_by_mast_root(&mast_root) {
847            Some(proc) if matches!(kind, InvokeKind::SysCall) => {
848                // Verify if this is a syscall, that the callee is a kernel procedure
849                //
850                // NOTE: The assembler is expected to know the full set of all kernel
851                // procedures at this point, so if we can't identify the callee as a
852                // kernel procedure, it is a definite error.
853                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                        // Note: this module is guaranteed to be of AST variant, since we have the
870                        // AST of a procedure contained in it (i.e. `proc`). Hence, it must be that
871                        // the entire module is in AST representation as well.
872                        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
890// HELPERS
891// ================================================================================================
892
893/// Contains a set of operations which need to be executed before and after a sequence of AST
894/// nodes (i.e., code body).
895struct BodyWrapper {
896    prologue: Vec<Operation>,
897    epilogue: Vec<Operation>,
898}