miden_assembly/assembler/
mod.rs

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// ASSEMBLER
44// ================================================================================================
45
46/// The [Assembler] is the primary interface for compiling Miden Assembly to the Miden Abstract
47/// 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}
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
89// ------------------------------------------------------------------------------------------------
90/// Constructors
91impl Assembler {
92    /// Start building an [Assembler]
93    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    /// Start building an [`Assembler`] with a kernel defined by the provided [KernelLibrary].
104    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    /// Sets the default behavior of this assembler with regard to warning diagnostics.
115    ///
116    /// When true, any warning diagnostics that are emitted will be promoted to errors.
117    pub fn with_warnings_as_errors(mut self, yes: bool) -> Self {
118        self.warnings_as_errors = yes;
119        self
120    }
121
122    /// Puts the assembler into the debug mode.
123    pub fn with_debug_mode(mut self, yes: bool) -> Self {
124        self.in_debug_mode = yes;
125        self
126    }
127
128    /// Sets the debug mode flag of the assembler
129    pub fn set_debug_mode(&mut self, yes: bool) {
130        self.in_debug_mode = yes;
131    }
132
133    /// Adds `module` to the module graph of the assembler.
134    ///
135    /// The given module must be a library module, or an error will be returned.
136    #[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    /// Adds `module` to the module graph of the assembler with the given options.
144    ///
145    /// The given module must be a library module, or an error will be returned.
146    #[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    /// Adds `module` to the module graph of the assembler.
158    ///
159    /// The given module must be a library module, or an error will be returned.
160    #[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    /// Adds `module` to the module graph of the assembler, using the provided options.
166    ///
167    /// The given module must be a library or kernel module, or an error will be returned
168    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    /// Adds all modules (defined by ".masm" files) from the specified directory to the module
203    /// of this assembler graph.
204    ///
205    /// The modules will be added under the specified namespace, but otherwise preserving the
206    /// structure of the directory. Any module named `mod.masm` will be added using parent
207    /// directory path For example, if `namespace` = "ns", modules from the ~/masm directory
208    /// will be added as follows:
209    ///
210    /// - ~/masm/foo.masm        -> "ns::foo"
211    /// - ~/masm/bar/mod.masm    -> "ns::bar"
212    /// - ~/masm/bar/baz.masm    -> "ns::bar::baz"
213    #[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    /// Adds the compiled library to provide modules for the compilation.
225    ///
226    /// We only current support adding non-vendored libraries - that is, the source code of exported
227    /// procedures is not included in the program that compiles against the library. The library's
228    /// source code is instead expected to be loaded in the processor at execution time. Hence, all
229    /// calls to library procedures will be compiled down to a [`vm_core::mast::ExternalNode`] (i.e.
230    /// a reference to the procedure's MAST root). This means that when executing a program compiled
231    /// against a library, the processor will not be able to differentiate procedures with the same
232    /// MAST root but different decorators.
233    ///
234    /// Hence, it is not recommended to export two procedures that have the same MAST root (i.e. are
235    /// identical except for their decorators). Note however that we don't expect this scenario to
236    /// be frequent in practice. For example, this could occur when APIs are being renamed and/or
237    /// moved between modules, and for some deprecation period, the same is exported under both its
238    /// old and new paths. Or possibly with common small functions that are implemented by the main
239    /// program and one of its dependencies.
240    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    /// Adds the compiled library to provide modules for the compilation.
248    ///
249    /// See [`Self::add_library`] for more detailed information.
250    pub fn with_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
251        self.add_library(library)?;
252        Ok(self)
253    }
254}
255
256// ------------------------------------------------------------------------------------------------
257/// Public Accessors
258impl Assembler {
259    /// Returns true if this assembler promotes warning diagnostics as errors by default.
260    pub fn warnings_as_errors(&self) -> bool {
261        self.warnings_as_errors
262    }
263
264    /// Returns true if this assembler was instantiated in debug mode.
265    pub fn in_debug_mode(&self) -> bool {
266        self.in_debug_mode
267    }
268
269    /// Returns a reference to the kernel for this assembler.
270    ///
271    /// If the assembler was instantiated without a kernel, the internal kernel will be empty.
272    pub fn kernel(&self) -> &Kernel {
273        self.module_graph.kernel()
274    }
275
276    /// Returns a link to the source manager used by this assembler.
277    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
288// ------------------------------------------------------------------------------------------------
289/// Compilation/Assembly
290impl Assembler {
291    /// Assembles a set of modules into a [Library].
292    ///
293    /// # Errors
294    ///
295    /// Returns an error if parsing or compilation of the specified modules fails.
296    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                // Note: it is safe to use `unwrap_ast()` here, since all of the modules contained
310                // in `ast_module_indices` are in AST form by definition.
311                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    /// Assembles the provided module into a [KernelLibrary] intended to be used as a Kernel.
353    ///
354    /// # Errors
355    ///
356    /// Returns an error if parsing or compilation of the specified modules fails.
357    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    /// Compiles the provided module into a [`Program`]. The resulting program can be executed on
368    /// Miden VM.
369    ///
370    /// # Errors
371    ///
372    /// Returns an error if parsing or compilation of the specified program fails, or if the source
373    /// doesn't have an entrypoint.
374    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        // Recompute graph with executable module, and start compiling
385        let ast_module_index = self.module_graph.add_ast_module(program)?;
386
387        // Find the executable entrypoint Note: it is safe to use `unwrap_ast()` here, since this is
388        // the module we just added, which is in AST representation.
389        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        // Compile the module graph rooted at the entrypoint
396        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        // in case the node IDs changed, update the entrypoint ID to the new value
404        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    /// Compile the uncompiled procedure in the module graph which are members of the subgraph
417    /// rooted at `root`, placing them in the MAST forest builder once compiled.
418    ///
419    /// Returns an error if any of the provided Miden Assembly is invalid.
420    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    /// Compiles all procedures in the `worklist`.
448    fn process_graph_worklist(
449        &mut self,
450        worklist: &mut Vec<GlobalProcedureIndex>,
451        mast_forest_builder: &mut MastForestBuilder,
452    ) -> Result<(), Report> {
453        // Process the topological ordering in reverse order (bottom-up), so that
454        // each procedure is compiled with all of its dependencies fully compiled
455        while let Some(procedure_gid) = worklist.pop() {
456            // If we have already compiled this procedure, do not recompile
457            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            // Fetch procedure metadata from the graph
462            let module = match &self.module_graph[procedure_gid.module] {
463                WrappedModule::Ast(ast_module) => ast_module,
464                // Note: if the containing module is in `Info` representation, there is nothing to
465                // compile.
466                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                    // Compile this procedure
489                    let procedure = self.compile_procedure(pctx, mast_forest_builder)?;
490                    // TODO: if a re-exported procedure with the same MAST root had been previously
491                    // added to the builder, this will result in unreachable nodes added to the
492                    // MAST forest. This is because while we won't insert a duplicate node for the
493                    // procedure body node itself, all nodes that make up the procedure body would
494                    // be added to the forest.
495
496                    // Cache the compiled procedure
497                    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                    // Make the MAST root available to all dependents
528                    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    /// Compiles a single Miden Assembly procedure to its MAST representation.
538    fn compile_procedure(
539        &self,
540        mut proc_ctx: ProcedureContext,
541        mast_forest_builder: &mut MastForestBuilder,
542    ) -> Result<Procedure, Report> {
543        // Make sure the current procedure context is available during codegen
544        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            // For procedures with locals, we need to update fmp register before and after the
552            // procedure body is executed. Specifically:
553            // - to allocate procedure locals we need to increment fmp by the number of locals
554            //   (rounded up to the word size), and
555            // - to deallocate procedure locals we need to decrement it by the same amount.
556            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                        // Attach the decorators before the first instance of the repeated node
648                        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                    // the procedure body ends with a list of decorators
699                    Some(decorator_ids)
700                },
701                BasicBlockOrDecorators::Nothing => None,
702            };
703
704        let procedure_body_id = if body_node_ids.is_empty() {
705            // We cannot allow only decorators in a procedure body, since decorators don't change
706            // the MAST digest of a node. Hence, two empty procedures with different decorators
707            // would look the same to the `MastForestBuilder`.
708            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        // Make sure that any post decorators are added at the end of the procedure body
721        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    /// Resolves the specified target to the corresponding procedure root [`MastNodeId`].
729    ///
730    /// If no [`MastNodeId`] exists for that procedure root, we wrap the root in an
731    /// [`crate::mast::ExternalNode`], and return the resulting [`MastNodeId`].
732    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                    // We didn't find the procedure in our current MAST forest. We still need to
756                    // check if it exists in one of a library dependency.
757                    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    /// Verifies the validity of the MAST root as a procedure root hash, and returns the ID of the
773    /// [`core::mast::ExternalNode`] that wraps it.
774    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        // Get the procedure from the assembler
782        let current_source_file = self.source_manager.get(span.source_id()).ok();
783
784        // If the procedure is cached and is a system call, ensure that the call is valid.
785        match mast_forest_builder.find_procedure_by_mast_root(&mast_root) {
786            Some(proc) if matches!(kind, InvokeKind::SysCall) => {
787                // Verify if this is a syscall, that the callee is a kernel procedure
788                //
789                // NOTE: The assembler is expected to know the full set of all kernel
790                // procedures at this point, so if we can't identify the callee as a
791                // kernel procedure, it is a definite error.
792                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                        // Note: this module is guaranteed to be of AST variant, since we have the
809                        // AST of a procedure contained in it (i.e. `proc`). Hence, it must be that
810                        // the entire module is in AST representation as well.
811                        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
829// HELPERS
830// ================================================================================================
831
832/// Contains a set of operations which need to be executed before and after a sequence of AST
833/// nodes (i.e., code body).
834struct BodyWrapper {
835    prologue: Vec<Operation>,
836    epilogue: Vec<Operation>,
837}