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 + Send + Sync>,
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 + 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    /// Start building an [`Assembler`] with a kernel defined by the provided [KernelLibrary].
108    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    /// Sets the default behavior of this assembler with regard to warning diagnostics.
122    ///
123    /// When true, any warning diagnostics that are emitted will be promoted to errors.
124    pub fn with_warnings_as_errors(mut self, yes: bool) -> Self {
125        self.warnings_as_errors = yes;
126        self
127    }
128
129    /// Puts the assembler into the debug mode.
130    pub fn with_debug_mode(mut self, yes: bool) -> Self {
131        self.in_debug_mode = yes;
132        self
133    }
134
135    /// Sets the debug mode flag of the assembler
136    pub fn set_debug_mode(&mut self, yes: bool) {
137        self.in_debug_mode = yes;
138    }
139
140    /// Adds `module` to the module graph of the assembler.
141    ///
142    /// The given module must be a library module, or an error will be returned.
143    #[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    /// Adds `module` to the module graph of the assembler with the given options.
151    ///
152    /// The given module must be a library module, or an error will be returned.
153    #[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    /// Adds `module` to the module graph of the assembler.
165    ///
166    /// The given module must be a library module, or an error will be returned.
167    #[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    /// Adds `module` to the module graph of the assembler, using the provided options.
173    ///
174    /// The given module must be a library or kernel module, or an error will be returned.
175    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    /// Adds a set of modules to the module graph of the assembler, using the provided options.
185    ///
186    /// The modules must all be library or kernel modules, or an error will be returned.
187    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    /// Adds all modules (defined by ".masm" files) from the specified directory to the module
213    /// of this assembler graph.
214    ///
215    /// The modules will be added under the specified namespace, but otherwise preserving the
216    /// structure of the directory. Any module named `mod.masm` will be added using parent
217    /// directory path For example, if `namespace` = "ns", modules from the ~/masm directory
218    /// will be added as follows:
219    ///
220    /// - ~/masm/foo.masm        -> "ns::foo"
221    /// - ~/masm/bar/mod.masm    -> "ns::bar"
222    /// - ~/masm/bar/baz.masm    -> "ns::bar::baz"
223    #[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    /// Adds the compiled library to provide modules for the compilation.
235    ///
236    /// All calls to the library's procedures will be compiled down to a
237    /// [`vm_core::mast::ExternalNode`] (i.e. a reference to the procedure's MAST root).
238    /// The library's source code is expected to be loaded in the processor at execution time.
239    /// This means that when executing a program compiled against a library, the processor will not
240    /// be able to differentiate procedures with the same MAST root but different decorators.
241    ///
242    /// Hence, it is not recommended to export two procedures that have the same MAST root (i.e. are
243    /// identical except for their decorators). Note however that we don't expect this scenario to
244    /// be frequent in practice. For example, this could occur when APIs are being renamed and/or
245    /// moved between modules, and for some deprecation period, the same is exported under both its
246    /// old and new paths. Or possibly with common small functions that are implemented by the main
247    /// program and one of its dependencies.
248    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    /// Adds the compiled library to provide modules for the compilation.
256    ///
257    /// See [`Self::add_library`] for more detailed information.
258    pub fn with_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
259        self.add_library(library)?;
260        Ok(self)
261    }
262
263    /// Adds a compiled library from which procedures will be vendored into the assembled code.
264    ///
265    /// Vendoring in this context means that when a procedure from this library is invoked from the
266    /// assembled code, the entire procedure MAST will be copied into the assembled code. Thus,
267    /// when the resulting code is executed on the VM, the vendored library does not need to be
268    /// provided to the VM to resolve external calls.
269    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    /// Adds a compiled library from which procedures will be vendored into the assembled code.
277    ///
278    /// See [`Self::add_vendored_library`]
279    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
285// ------------------------------------------------------------------------------------------------
286/// Public Accessors
287impl Assembler {
288    /// Returns true if this assembler promotes warning diagnostics as errors by default.
289    pub fn warnings_as_errors(&self) -> bool {
290        self.warnings_as_errors
291    }
292
293    /// Returns true if this assembler was instantiated in debug mode.
294    pub fn in_debug_mode(&self) -> bool {
295        self.in_debug_mode
296    }
297
298    /// Returns a reference to the kernel for this assembler.
299    ///
300    /// If the assembler was instantiated without a kernel, the internal kernel will be empty.
301    pub fn kernel(&self) -> &Kernel {
302        self.module_graph.kernel()
303    }
304
305    /// Returns a link to the source manager used by this assembler.
306    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
317// ------------------------------------------------------------------------------------------------
318/// Compilation/Assembly
319impl Assembler {
320    /// Shared code used by both [Assembler::assemble_library()] and [Assembler::assemble_kernel()].
321    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                // Note: it is safe to use `unwrap_ast()` here, since all of the modules contained
335                // in `ast_module_indices` are in AST form by definition.
336                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    /// Assembles a set of modules into a [Library].
364    ///
365    /// # Errors
366    ///
367    /// Returns an error if parsing or compilation of the specified modules fails.
368    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    /// Assembles the provided module into a [KernelLibrary] intended to be used as a Kernel.
381    ///
382    /// # Errors
383    ///
384    /// Returns an error if parsing or compilation of the specified modules fails.
385    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    /// Compiles the provided module into a [`Program`]. The resulting program can be executed on
396    /// Miden VM.
397    ///
398    /// # Errors
399    ///
400    /// Returns an error if parsing or compilation of the specified program fails, or if the source
401    /// doesn't have an entrypoint.
402    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        // Recompute graph with executable module, and start compiling
413        let ast_module_index = self.module_graph.add_ast_module(program)?;
414
415        // Find the executable entrypoint Note: it is safe to use `unwrap_ast()` here, since this is
416        // the module we just added, which is in AST representation.
417        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        // Compile the module graph rooted at the entrypoint
424        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        // in case the node IDs changed, update the entrypoint ID to the new value
433        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    /// Compile the uncompiled procedure in the module graph which are members of the subgraph
444    /// rooted at `root`, placing them in the MAST forest builder once compiled.
445    ///
446    /// Returns an error if any of the provided Miden Assembly is invalid.
447    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    /// Compiles all procedures in the `worklist`.
475    fn process_graph_worklist(
476        &mut self,
477        worklist: &mut Vec<GlobalProcedureIndex>,
478        mast_forest_builder: &mut MastForestBuilder,
479    ) -> Result<(), Report> {
480        // Process the topological ordering in reverse order (bottom-up), so that
481        // each procedure is compiled with all of its dependencies fully compiled
482        while let Some(procedure_gid) = worklist.pop() {
483            // If we have already compiled this procedure, do not recompile
484            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            // Fetch procedure metadata from the graph
489            let module = match &self.module_graph[procedure_gid.module] {
490                WrappedModule::Ast(ast_module) => ast_module,
491                // Note: if the containing module is in `Info` representation, there is nothing to
492                // compile.
493                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                    // Compile this procedure
516                    let procedure = self.compile_procedure(pctx, mast_forest_builder)?;
517                    // TODO: if a re-exported procedure with the same MAST root had been previously
518                    // added to the builder, this will result in unreachable nodes added to the
519                    // MAST forest. This is because while we won't insert a duplicate node for the
520                    // procedure body node itself, all nodes that make up the procedure body would
521                    // be added to the forest.
522
523                    // Cache the compiled procedure
524                    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                    // Make the MAST root available to all dependents
555                    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    /// Compiles a single Miden Assembly procedure to its MAST representation.
565    fn compile_procedure(
566        &self,
567        mut proc_ctx: ProcedureContext,
568        mast_forest_builder: &mut MastForestBuilder,
569    ) -> Result<Procedure, Report> {
570        // Make sure the current procedure context is available during codegen
571        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            // For procedures with locals, we need to update fmp register before and after the
579            // procedure body is executed. Specifically:
580            // - to allocate procedure locals we need to increment fmp by the number of locals
581            //   (rounded up to the word size), and
582            // - to deallocate procedure locals we need to decrement it by the same amount.
583            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                    // Add an assembly operation decorator to the if node in debug mode.
659                    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                        // Attach the decorators before the first instance of the repeated node
692                        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                    // Add an assembly operation decorator to the loop node in debug mode.
731                    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                    // the procedure body ends with a list of decorators
760                    Some(decorator_ids)
761                },
762                BasicBlockOrDecorators::Nothing => None,
763            };
764
765        let procedure_body_id = if body_node_ids.is_empty() {
766            // We cannot allow only decorators in a procedure body, since decorators don't change
767            // the MAST digest of a node. Hence, two empty procedures with different decorators
768            // would look the same to the `MastForestBuilder`.
769            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        // Make sure that any post decorators are added at the end of the procedure body
782        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    /// Resolves the specified target to the corresponding procedure root [`MastNodeId`].
790    ///
791    /// If no [`MastNodeId`] exists for that procedure root, we wrap the root in an
792    /// [`crate::mast::ExternalNode`], and return the resulting [`MastNodeId`].
793    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                    // We didn't find the procedure in our current MAST forest. We still need to
817                    // check if it exists in one of a library dependency.
818                    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    /// Verifies the validity of the MAST root as a procedure root hash, and adds it to the forest.
835    ///
836    /// If the root is present in the vendored MAST, its subtree is copied. Otherwise an
837    /// external node is added to the forest.
838    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        // Get the procedure from the assembler
846        let current_source_file = self.source_manager.get(span.source_id()).ok();
847
848        // If the procedure is cached and is a system call, ensure that the call is valid.
849        match mast_forest_builder.find_procedure_by_mast_root(&mast_root) {
850            Some(proc) if matches!(kind, InvokeKind::SysCall) => {
851                // Verify if this is a syscall, that the callee is a kernel procedure
852                //
853                // NOTE: The assembler is expected to know the full set of all kernel
854                // procedures at this point, so if we can't identify the callee as a
855                // kernel procedure, it is a definite error.
856                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                        // Note: this module is guaranteed to be of AST variant, since we have the
873                        // AST of a procedure contained in it (i.e. `proc`). Hence, it must be that
874                        // the entire module is in AST representation as well.
875                        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
893// HELPERS
894// ================================================================================================
895
896/// Contains a set of operations which need to be executed before and after a sequence of AST
897/// nodes (i.e., code body).
898struct BodyWrapper {
899    prologue: Vec<Operation>,
900    epilogue: Vec<Operation>,
901}