miden_assembly/
assembler.rs

1use alloc::{collections::BTreeMap, string::ToString, sync::Arc, vec::Vec};
2
3use miden_assembly_syntax::{
4    KernelLibrary, Library, LibraryNamespace, LibraryPath, Parse, ParseOptions,
5    SemanticAnalysisError,
6    ast::{self, Export, InvocationTarget, InvokeKind, ModuleKind, QualifiedProcedureName},
7    debuginfo::{DefaultSourceManager, SourceManager, SourceSpan, Spanned},
8    diagnostics::{RelatedLabel, Report},
9};
10use miden_core::{
11    AssemblyOp, Decorator, Felt, Kernel, Operation, Program, WORD_SIZE, Word,
12    mast::{DecoratorId, MastNodeId},
13};
14
15use crate::{
16    GlobalProcedureIndex, ModuleIndex, Procedure, ProcedureContext,
17    basic_block_builder::{BasicBlockBuilder, BasicBlockOrDecorators},
18    linker::{
19        CallerInfo, LinkLibrary, LinkLibraryKind, Linker, LinkerError, ModuleLink, ProcedureLink,
20        ResolvedTarget,
21    },
22    mast_forest_builder::MastForestBuilder,
23};
24
25// ASSEMBLER
26// ================================================================================================
27
28/// The [Assembler] produces a _Merkelized Abstract Syntax Tree (MAST)_ from Miden Assembly sources,
29/// as an artifact of one of three types:
30///
31/// * A kernel library (see [`KernelLibrary`])
32/// * A library (see [`Library`])
33/// * A program (see [`Program`])
34///
35/// Assembled artifacts can additionally reference or include code from previously assembled
36/// libraries.
37///
38/// # Usage
39///
40/// Depending on your needs, there are multiple ways of using the assembler, starting with the
41/// type of artifact you want to produce:
42///
43/// * If you wish to produce an executable program, you will call [`Self::assemble_program`] with
44///   the source module which contains the program entrypoint.
45/// * If you wish to produce a library for use in other executables, you will call
46///   [`Self::assemble_library`] with the source module(s) whose exports form the public API of the
47///   library.
48/// * If you wish to produce a kernel library, you will call [`Self::assemble_kernel`] with the
49///   source module(s) whose exports form the public API of the kernel.
50///
51/// In the case where you are assembling a library or program, you also need to determine if you
52/// need to specify a kernel. You will need to do so if any of your code needs to call into the
53/// kernel directly.
54///
55/// * If a kernel is needed, you should construct an `Assembler` using [`Assembler::with_kernel`]
56/// * Otherwise, you should construct an `Assembler` using [`Assembler::new`]
57///
58/// <div class="warning">
59/// Programs compiled with an empty kernel cannot use the `syscall` instruction.
60/// </div>
61///
62/// Lastly, you need to provide inputs to the assembler which it will use at link time to resolve
63/// references to procedures which are externally-defined (i.e. not defined in any of the modules
64/// provided to the `assemble_*` function you called). There are a few different ways to do this:
65///
66/// * If you have source code, or a [`ast::Module`], see [`Self::compile_and_statically_link`]
67/// * If you need to reference procedures from a previously assembled [`Library`], but do not want
68///   to include the MAST of those procedures in the assembled artifact, you want to _dynamically
69///   link_ that library, see [`Self::link_dynamic_library`] for more.
70/// * If you want to incorporate referenced procedures from a previously assembled [`Library`] into
71///   the assembled artifact, you want to _statically link_ that library, see
72///   [`Self::link_static_library`] for more.
73#[derive(Clone)]
74pub struct Assembler {
75    /// The source manager to use for compilation and source location information
76    source_manager: Arc<dyn SourceManager + Send + Sync>,
77    /// The linker instance used internally to link assembler inputs
78    linker: Linker,
79    /// Whether to treat warning diagnostics as errors
80    warnings_as_errors: bool,
81    /// Whether the assembler enables extra debugging information.
82    in_debug_mode: bool,
83}
84
85impl Default for Assembler {
86    fn default() -> Self {
87        let source_manager = Arc::new(DefaultSourceManager::default());
88        let linker = Linker::new(source_manager.clone());
89        Self {
90            source_manager,
91            linker,
92            warnings_as_errors: false,
93            in_debug_mode: false,
94        }
95    }
96}
97
98// ------------------------------------------------------------------------------------------------
99/// Constructors
100impl Assembler {
101    /// Start building an [Assembler]
102    pub fn new(source_manager: Arc<dyn SourceManager + Send + Sync>) -> Self {
103        let linker = Linker::new(source_manager.clone());
104        Self {
105            source_manager,
106            linker,
107            warnings_as_errors: false,
108            in_debug_mode: false,
109        }
110    }
111
112    /// Start building an [`Assembler`] with a kernel defined by the provided [KernelLibrary].
113    pub fn with_kernel(
114        source_manager: Arc<dyn SourceManager + Send + Sync>,
115        kernel_lib: KernelLibrary,
116    ) -> Self {
117        let (kernel, kernel_module, _) = kernel_lib.into_parts();
118        let linker = Linker::with_kernel(source_manager.clone(), kernel, kernel_module);
119        Self {
120            source_manager,
121            linker,
122            ..Default::default()
123        }
124    }
125
126    /// Sets the default behavior of this assembler with regard to warning diagnostics.
127    ///
128    /// When true, any warning diagnostics that are emitted will be promoted to errors.
129    pub fn with_warnings_as_errors(mut self, yes: bool) -> Self {
130        self.warnings_as_errors = yes;
131        self
132    }
133
134    /// Puts the assembler into the debug mode.
135    pub fn with_debug_mode(mut self, yes: bool) -> Self {
136        self.in_debug_mode = yes;
137        self
138    }
139
140    /// Sets the debug mode flag of the assembler
141    pub fn set_debug_mode(&mut self, yes: bool) {
142        self.in_debug_mode = yes;
143    }
144}
145
146// ------------------------------------------------------------------------------------------------
147/// Dependency Management
148impl Assembler {
149    /// Ensures `module` is compiled, and then statically links it into the final artifact.
150    ///
151    /// The given module must be a library module, or an error will be returned.
152    #[inline]
153    pub fn compile_and_statically_link(&mut self, module: impl Parse) -> Result<&mut Self, Report> {
154        self.compile_and_statically_link_all([module])
155    }
156
157    /// Ensures every module in `modules` is compiled, and then statically links them into the final
158    /// artifact.
159    ///
160    /// All of the given modules must be library modules, or an error will be returned.
161    pub fn compile_and_statically_link_all(
162        &mut self,
163        modules: impl IntoIterator<Item = impl Parse>,
164    ) -> Result<&mut Self, Report> {
165        let modules = modules
166            .into_iter()
167            .map(|module| {
168                module.parse_with_options(
169                    &self.source_manager,
170                    ParseOptions {
171                        warnings_as_errors: self.warnings_as_errors,
172                        ..ParseOptions::for_library()
173                    },
174                )
175            })
176            .collect::<Result<Vec<_>, Report>>()?;
177
178        self.linker.link_modules(modules)?;
179
180        Ok(self)
181    }
182
183    /// Compiles all Miden Assembly modules in the provided directory, and then statically links
184    /// them into the final artifact.
185    ///
186    /// When compiling each module, the path of the module is derived by appending path components
187    /// corresponding to the relative path of the module in `dir`, to `namespace`. If a source file
188    /// named `mod.masm` is found, the resulting module will derive its path using the path
189    /// components of the parent directory, rather than the file name.
190    ///
191    /// For example, let's assume we call this function with the namespace `my_lib`, for a
192    /// directory at path `~/masm`. Now, let's look at how various file system paths would get
193    /// translated to their corresponding module paths:
194    ///
195    /// | file path           | module path        |
196    /// |---------------------|--------------------|
197    /// | ~/masm/mod.masm     | "my_lib"           |
198    /// | ~/masm/foo.masm     | "my_lib::foo"      |
199    /// | ~/masm/bar/mod.masm | "my_lib::bar"      |
200    /// | ~/masm/bar/baz.masm | "my_lib::bar::baz" |
201    #[cfg(feature = "std")]
202    pub fn compile_and_statically_link_from_dir(
203        &mut self,
204        namespace: crate::LibraryNamespace,
205        dir: impl AsRef<std::path::Path>,
206    ) -> Result<(), Report> {
207        use miden_assembly_syntax::parser;
208
209        let modules = parser::read_modules_from_dir(namespace, dir, &self.source_manager)?;
210        self.linker.link_modules(modules)?;
211        Ok(())
212    }
213
214    /// Links the final artifact against `library`.
215    ///
216    /// The way in which procedures referenced in `library` will be linked by the final artifact is
217    /// determined by `kind`:
218    ///
219    /// * [`LinkLibraryKind::Dynamic`] inserts a reference to the procedure in the assembled MAST,
220    ///   but not the MAST of the procedure itself. Consequently, it is necessary to provide both
221    ///   the assembled artifact _and_ `library` to the VM when executing the program, otherwise the
222    ///   procedure reference will not be resolvable at runtime.
223    /// * [`LinkLibraryKind::Static`] includes the MAST of the referenced procedure in the final
224    ///   artifact, including any code reachable from that procedure contained in `library`. The
225    ///   resulting artifact does not require `library` to be provided to the VM when executing it,
226    ///   as all procedure references were resolved ahead of time.
227    pub fn link_library(
228        &mut self,
229        library: impl AsRef<Library>,
230        kind: LinkLibraryKind,
231    ) -> Result<(), Report> {
232        self.linker
233            .link_library(LinkLibrary {
234                library: Arc::new(library.as_ref().clone()),
235                kind,
236            })
237            .map_err(Report::from)
238    }
239
240    /// Dynamically link against `library` during assembly.
241    ///
242    /// This makes it possible to resolve references to procedures exported by the library during
243    /// assembly, without including code from the library into the assembled artifact.
244    ///
245    /// Dynamic linking produces smaller binaries, but requires you to provide `library` to the VM
246    /// at runtime when executing the assembled artifact.
247    ///
248    /// Internally, calls to procedures exported from `library` will be lowered to a
249    /// [`miden_core::mast::ExternalNode`] in the resulting MAST. These nodes represent an indirect
250    /// reference to the root MAST node of the referenced procedure. These indirect references
251    /// are resolved at runtime by the processor when executed.
252    ///
253    /// One consequence of these types of references, is that in the case where multiple procedures
254    /// have the same MAST root, but different decorators, it is not (currently) possible for the
255    /// processor to distinguish between which specific procedure (and its resulting decorators) the
256    /// caller intended to reference, and so any of them might be chosen.
257    ///
258    /// In order to reduce the chance of this producing confusing diagnostics or debugger output,
259    /// it is not recommended to export multiple procedures with the same MAST root, but differing
260    /// decorators, from a library. There are scenarios where this might be necessary, such as when
261    /// renaming a procedure, or moving it between modules, while keeping the original definition
262    /// around during a deprecation period. It is just something to be aware of if you notice, for
263    /// example, unexpected procedure paths or source locations in diagnostics - it could be due
264    /// to this edge case.
265    pub fn link_dynamic_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> {
266        self.linker
267            .link_library(LinkLibrary::dynamic(Arc::new(library.as_ref().clone())))
268            .map_err(Report::from)
269    }
270
271    /// Dynamically link against `library` during assembly.
272    ///
273    /// See [`Self::link_dynamic_library`] for more details.
274    pub fn with_dynamic_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
275        self.link_dynamic_library(library)?;
276        Ok(self)
277    }
278
279    /// Statically link against `library` during assembly.
280    ///
281    /// This makes it possible to resolve references to procedures exported by the library during
282    /// assembly, and ensure that the referenced procedure and any code reachable from it in that
283    /// library, are included in the assembled artifact.
284    ///
285    /// Static linking produces larger binaries, but allows you to produce self-contained artifacts
286    /// that avoid the requirement that you provide `library` to the VM at runtime.
287    pub fn link_static_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> {
288        self.linker
289            .link_library(LinkLibrary::r#static(Arc::new(library.as_ref().clone())))
290            .map_err(Report::from)
291    }
292
293    /// Statically link against `library` during assembly.
294    ///
295    /// See [`Self::link_static_library`]
296    pub fn with_static_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> {
297        self.link_static_library(library)?;
298        Ok(self)
299    }
300}
301
302// ------------------------------------------------------------------------------------------------
303/// Public Accessors
304impl Assembler {
305    /// Returns true if this assembler promotes warning diagnostics as errors by default.
306    pub fn warnings_as_errors(&self) -> bool {
307        self.warnings_as_errors
308    }
309
310    /// Returns true if this assembler was instantiated in debug mode.
311    pub fn in_debug_mode(&self) -> bool {
312        self.in_debug_mode
313    }
314
315    /// Returns a reference to the kernel for this assembler.
316    ///
317    /// If the assembler was instantiated without a kernel, the internal kernel will be empty.
318    pub fn kernel(&self) -> &Kernel {
319        self.linker.kernel()
320    }
321
322    /// Returns a link to the source manager used by this assembler.
323    pub fn source_manager(&self) -> Arc<dyn SourceManager + Send + Sync> {
324        self.source_manager.clone()
325    }
326
327    #[cfg(any(test, feature = "testing"))]
328    #[doc(hidden)]
329    pub fn linker(&self) -> &Linker {
330        &self.linker
331    }
332}
333
334// ------------------------------------------------------------------------------------------------
335/// Compilation/Assembly
336impl Assembler {
337    /// Assembles a set of modules into a [Library].
338    ///
339    /// # Errors
340    ///
341    /// Returns an error if parsing or compilation of the specified modules fails.
342    pub fn assemble_library(
343        mut self,
344        modules: impl IntoIterator<Item = impl Parse>,
345    ) -> Result<Library, Report> {
346        let modules = modules
347            .into_iter()
348            .map(|module| {
349                module.parse_with_options(
350                    &self.source_manager,
351                    ParseOptions {
352                        warnings_as_errors: self.warnings_as_errors,
353                        ..ParseOptions::for_library()
354                    },
355                )
356            })
357            .collect::<Result<Vec<_>, Report>>()?;
358
359        let module_indices = self.linker.link(modules)?;
360
361        self.assemble_common(&module_indices)
362    }
363
364    /// Assemble a [Library] from a standard Miden Assembly project layout.
365    ///
366    /// The standard layout dictates that a given path is the root of a namespace, and the
367    /// directory hierarchy corresponds to the namespace hierarchy. A `.masm` file found in a
368    /// given subdirectory of the root, will be parsed with its [LibraryPath] set based on
369    /// where it resides in the directory structure.
370    ///
371    /// This function recursively parses the entire directory structure under `path`, ignoring
372    /// any files which do not have the `.masm` extension.
373    ///
374    /// For example, let's say I call this function like so:
375    ///
376    /// ```rust
377    /// use miden_assembly::{Assembler, LibraryNamespace};
378    ///
379    /// Assembler::default()
380    ///     .assemble_library_from_dir("~/masm/std", LibraryNamespace::new("std").unwrap());
381    /// ```
382    ///
383    /// Here's how we would handle various files under this path:
384    ///
385    /// - ~/masm/std/sys.masm            -> Parsed as "std::sys"
386    /// - ~/masm/std/crypto/hash.masm    -> Parsed as "std::crypto::hash"
387    /// - ~/masm/std/math/u32.masm       -> Parsed as "std::math::u32"
388    /// - ~/masm/std/math/u64.masm       -> Parsed as "std::math::u64"
389    /// - ~/masm/std/math/README.md      -> Ignored
390    #[cfg(feature = "std")]
391    pub fn assemble_library_from_dir(
392        self,
393        path: impl AsRef<std::path::Path>,
394        namespace: LibraryNamespace,
395    ) -> Result<Library, Report> {
396        use miden_assembly_syntax::parser;
397
398        let path = path.as_ref();
399
400        let source_manager = self.source_manager.clone();
401        let modules = parser::read_modules_from_dir(namespace, path, &source_manager)?;
402        self.assemble_library(modules)
403    }
404
405    /// Assembles the provided module into a [KernelLibrary] intended to be used as a Kernel.
406    ///
407    /// # Errors
408    ///
409    /// Returns an error if parsing or compilation of the specified modules fails.
410    pub fn assemble_kernel(mut self, module: impl Parse) -> Result<KernelLibrary, Report> {
411        let module = module.parse_with_options(
412            &self.source_manager,
413            ParseOptions {
414                path: Some(LibraryPath::new_from_components(LibraryNamespace::Kernel, [])),
415                warnings_as_errors: self.warnings_as_errors,
416                ..ParseOptions::for_kernel()
417            },
418        )?;
419
420        let module_indices = self.linker.link_kernel(module)?;
421
422        self.assemble_common(&module_indices)
423            .and_then(|lib| KernelLibrary::try_from(lib).map_err(Report::new))
424    }
425
426    /// Assemble a [KernelLibrary] from a standard Miden Assembly kernel project layout.
427    ///
428    /// The kernel library will export procedures defined by the module at `sys_module_path`.
429    ///
430    /// If the optional `lib_dir` is provided, all modules under this directory will be available
431    /// from the kernel module under the `$kernel` namespace. For example, if `lib_dir` is set to
432    /// "~/masm/lib", the files will be accessible in the kernel module as follows:
433    ///
434    /// - ~/masm/lib/foo.masm        -> Can be imported as "$kernel::foo"
435    /// - ~/masm/lib/bar/baz.masm    -> Can be imported as "$kernel::bar::baz"
436    ///
437    /// Note: this is a temporary structure which will likely change once
438    /// <https://github.com/0xMiden/miden-vm/issues/1436> is implemented.
439    #[cfg(feature = "std")]
440    pub fn assemble_kernel_from_dir(
441        mut self,
442        sys_module_path: impl AsRef<std::path::Path>,
443        lib_dir: Option<impl AsRef<std::path::Path>>,
444    ) -> Result<KernelLibrary, Report> {
445        // if library directory is provided, add modules from this directory to the assembler
446        if let Some(lib_dir) = lib_dir {
447            self.compile_and_statically_link_from_dir(LibraryNamespace::Kernel, lib_dir)?;
448        }
449
450        self.assemble_kernel(sys_module_path.as_ref())
451    }
452
453    /// Shared code used by both [`Self::assemble_library`] and [`Self::assemble_kernel`].
454    fn assemble_common(mut self, module_indices: &[ModuleIndex]) -> Result<Library, Report> {
455        let staticlibs = self.linker.libraries().filter_map(|lib| {
456            if matches!(lib.kind, LinkLibraryKind::Static) {
457                Some(lib.library.as_ref())
458            } else {
459                None
460            }
461        });
462        let mut mast_forest_builder = MastForestBuilder::new(staticlibs)?;
463        let mut exports = {
464            let mut exports = BTreeMap::new();
465
466            for module_idx in module_indices.iter().copied() {
467                // Note: it is safe to use `unwrap_ast()` here, since all of the modules contained
468                // in `module_indices` are in AST form by definition.
469                let ast_module = self.linker[module_idx].unwrap_ast().clone();
470
471                mast_forest_builder.merge_advice_map(ast_module.advice_map())?;
472
473                for (proc_idx, fqn) in ast_module.exported_procedures() {
474                    let gid = module_idx + proc_idx;
475                    self.compile_subgraph(gid, &mut mast_forest_builder)?;
476
477                    let proc_root_node_id = mast_forest_builder
478                        .get_procedure(gid)
479                        .expect("compilation succeeded but root not found in cache")
480                        .body_node_id();
481                    exports.insert(fqn, proc_root_node_id);
482                }
483            }
484
485            exports
486        };
487
488        let (mast_forest, id_remappings) = mast_forest_builder.build();
489        for (_proc_name, node_id) in exports.iter_mut() {
490            if let Some(&new_node_id) = id_remappings.get(node_id) {
491                *node_id = new_node_id;
492            }
493        }
494
495        Ok(Library::new(mast_forest.into(), exports)?)
496    }
497
498    /// Compiles the provided module into a [`Program`]. The resulting program can be executed on
499    /// Miden VM.
500    ///
501    /// # Errors
502    ///
503    /// Returns an error if parsing or compilation of the specified program fails, or if the source
504    /// doesn't have an entrypoint.
505    pub fn assemble_program(mut self, source: impl Parse) -> Result<Program, Report> {
506        let options = ParseOptions {
507            kind: ModuleKind::Executable,
508            warnings_as_errors: self.warnings_as_errors,
509            path: Some(LibraryPath::from(LibraryNamespace::Exec)),
510        };
511
512        let program = source.parse_with_options(&self.source_manager, options)?;
513        assert!(program.is_executable());
514
515        // Recompute graph with executable module, and start compiling
516        let module_index = self.linker.link([program])?[0];
517
518        // Find the executable entrypoint Note: it is safe to use `unwrap_ast()` here, since this is
519        // the module we just added, which is in AST representation.
520        let entrypoint = self.linker[module_index]
521            .unwrap_ast()
522            .index_of(|p| p.is_main())
523            .map(|index| GlobalProcedureIndex { module: module_index, index })
524            .ok_or(SemanticAnalysisError::MissingEntrypoint)?;
525
526        // Compile the linked module graph rooted at the entrypoint
527        let staticlibs = self.linker.libraries().filter_map(|lib| {
528            if matches!(lib.kind, LinkLibraryKind::Static) {
529                Some(lib.library.as_ref())
530            } else {
531                None
532            }
533        });
534        let mut mast_forest_builder = MastForestBuilder::new(staticlibs)?;
535
536        mast_forest_builder
537            .merge_advice_map(self.linker[module_index].unwrap_ast().advice_map())?;
538
539        self.compile_subgraph(entrypoint, &mut mast_forest_builder)?;
540        let entry_node_id = mast_forest_builder
541            .get_procedure(entrypoint)
542            .expect("compilation succeeded but root not found in cache")
543            .body_node_id();
544
545        // in case the node IDs changed, update the entrypoint ID to the new value
546        let (mast_forest, id_remappings) = mast_forest_builder.build();
547        let entry_node_id = *id_remappings.get(&entry_node_id).unwrap_or(&entry_node_id);
548
549        Ok(Program::with_kernel(
550            mast_forest.into(),
551            entry_node_id,
552            self.linker.kernel().clone(),
553        ))
554    }
555
556    /// Compile the uncompiled procedure in the linked module graph which are members of the
557    /// subgraph rooted at `root`, placing them in the MAST forest builder once compiled.
558    ///
559    /// Returns an error if any of the provided Miden Assembly is invalid.
560    fn compile_subgraph(
561        &mut self,
562        root: GlobalProcedureIndex,
563        mast_forest_builder: &mut MastForestBuilder,
564    ) -> Result<(), Report> {
565        let mut worklist: Vec<GlobalProcedureIndex> = self
566            .linker
567            .topological_sort_from_root(root)
568            .map_err(|cycle| {
569                let iter = cycle.into_node_ids();
570                let mut nodes = Vec::with_capacity(iter.len());
571                for node in iter {
572                    let module = self.linker[node.module].path();
573                    let proc = self.linker.get_procedure_unsafe(node);
574                    nodes.push(format!("{}::{}", module, proc.name()));
575                }
576                LinkerError::Cycle { nodes: nodes.into() }
577            })?
578            .into_iter()
579            .filter(|&gid| self.linker.get_procedure_unsafe(gid).is_ast())
580            .collect();
581
582        assert!(!worklist.is_empty());
583
584        self.process_graph_worklist(&mut worklist, mast_forest_builder)
585    }
586
587    /// Compiles all procedures in the `worklist`.
588    fn process_graph_worklist(
589        &mut self,
590        worklist: &mut Vec<GlobalProcedureIndex>,
591        mast_forest_builder: &mut MastForestBuilder,
592    ) -> Result<(), Report> {
593        // Process the topological ordering in reverse order (bottom-up), so that
594        // each procedure is compiled with all of its dependencies fully compiled
595        while let Some(procedure_gid) = worklist.pop() {
596            // If we have already compiled this procedure, do not recompile
597            if let Some(proc) = mast_forest_builder.get_procedure(procedure_gid) {
598                self.linker.register_procedure_root(procedure_gid, proc.mast_root())?;
599                continue;
600            }
601            // Fetch procedure metadata from the graph
602            let module = match &self.linker[procedure_gid.module] {
603                ModuleLink::Ast(ast_module) => ast_module,
604                // Note: if the containing module is in `Info` representation, there is nothing to
605                // compile.
606                ModuleLink::Info(_) => continue,
607            };
608
609            let export = &module[procedure_gid.index];
610            match export {
611                Export::Procedure(proc) => {
612                    let num_locals = proc.num_locals();
613                    let name = QualifiedProcedureName {
614                        span: proc.span(),
615                        module: module.path().clone(),
616                        name: proc.name().clone(),
617                    };
618                    let pctx = ProcedureContext::new(
619                        procedure_gid,
620                        name,
621                        proc.visibility(),
622                        module.is_in_kernel(),
623                        self.source_manager.clone(),
624                    )
625                    .with_num_locals(num_locals)
626                    .with_span(proc.span());
627
628                    // Compile this procedure
629                    let procedure = self.compile_procedure(pctx, mast_forest_builder)?;
630                    // TODO: if a re-exported procedure with the same MAST root had been previously
631                    // added to the builder, this will result in unreachable nodes added to the
632                    // MAST forest. This is because while we won't insert a duplicate node for the
633                    // procedure body node itself, all nodes that make up the procedure body would
634                    // be added to the forest.
635
636                    // Cache the compiled procedure
637                    self.linker.register_procedure_root(procedure_gid, procedure.mast_root())?;
638                    mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
639                },
640                Export::Alias(proc_alias) => {
641                    let name = QualifiedProcedureName {
642                        span: proc_alias.span(),
643                        module: module.path().clone(),
644                        name: proc_alias.name().clone(),
645                    };
646                    let pctx = ProcedureContext::new(
647                        procedure_gid,
648                        name,
649                        ast::Visibility::Public,
650                        module.is_in_kernel(),
651                        self.source_manager.clone(),
652                    )
653                    .with_span(proc_alias.span());
654
655                    let proc_node_id = self.resolve_target(
656                        InvokeKind::ProcRef,
657                        &proc_alias.target().into(),
658                        &pctx,
659                        mast_forest_builder,
660                    )?;
661                    let proc_mast_root =
662                        mast_forest_builder.get_mast_node(proc_node_id).unwrap().digest();
663
664                    let procedure = pctx.into_procedure(proc_mast_root, proc_node_id);
665
666                    // Make the MAST root available to all dependents
667                    self.linker.register_procedure_root(procedure_gid, proc_mast_root)?;
668                    mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
669                },
670            }
671        }
672
673        Ok(())
674    }
675
676    /// Compiles a single Miden Assembly procedure to its MAST representation.
677    fn compile_procedure(
678        &self,
679        mut proc_ctx: ProcedureContext,
680        mast_forest_builder: &mut MastForestBuilder,
681    ) -> Result<Procedure, Report> {
682        // Make sure the current procedure context is available during codegen
683        let gid = proc_ctx.id();
684
685        let num_locals = proc_ctx.num_locals();
686
687        let wrapper_proc = self.linker.get_procedure_unsafe(gid);
688        let proc = wrapper_proc.unwrap_ast().unwrap_procedure();
689        let proc_body_id = if num_locals > 0 {
690            // For procedures with locals, we need to update fmp register before and after the
691            // procedure body is executed. Specifically:
692            // - to allocate procedure locals we need to increment fmp by the number of locals
693            //   (rounded up to the word size), and
694            // - to deallocate procedure locals we need to decrement it by the same amount.
695            let locals_frame = Felt::from(num_locals.next_multiple_of(WORD_SIZE as u16));
696            let wrapper = BodyWrapper {
697                prologue: vec![Operation::Push(locals_frame), Operation::FmpUpdate],
698                epilogue: vec![Operation::Push(-locals_frame), Operation::FmpUpdate],
699            };
700            self.compile_body(proc.iter(), &mut proc_ctx, Some(wrapper), mast_forest_builder)?
701        } else {
702            self.compile_body(proc.iter(), &mut proc_ctx, None, mast_forest_builder)?
703        };
704
705        let proc_body_node = mast_forest_builder
706            .get_mast_node(proc_body_id)
707            .expect("no MAST node for compiled procedure");
708        Ok(proc_ctx.into_procedure(proc_body_node.digest(), proc_body_id))
709    }
710
711    fn compile_body<'a, I>(
712        &self,
713        body: I,
714        proc_ctx: &mut ProcedureContext,
715        wrapper: Option<BodyWrapper>,
716        mast_forest_builder: &mut MastForestBuilder,
717    ) -> Result<MastNodeId, Report>
718    where
719        I: Iterator<Item = &'a ast::Op>,
720    {
721        use ast::Op;
722
723        let mut body_node_ids: Vec<MastNodeId> = Vec::new();
724        let mut block_builder = BasicBlockBuilder::new(wrapper, mast_forest_builder);
725
726        for op in body {
727            match op {
728                Op::Inst(inst) => {
729                    if let Some(node_id) =
730                        self.compile_instruction(inst, &mut block_builder, proc_ctx)?
731                    {
732                        if let Some(basic_block_id) = block_builder.make_basic_block()? {
733                            body_node_ids.push(basic_block_id);
734                        } else if let Some(decorator_ids) = block_builder.drain_decorators() {
735                            block_builder
736                                .mast_forest_builder_mut()
737                                .append_before_enter(node_id, &decorator_ids);
738                        }
739
740                        body_node_ids.push(node_id);
741                    }
742                },
743
744                Op::If { then_blk, else_blk, span } => {
745                    if let Some(basic_block_id) = block_builder.make_basic_block()? {
746                        body_node_ids.push(basic_block_id);
747                    }
748
749                    let then_blk = self.compile_body(
750                        then_blk.iter(),
751                        proc_ctx,
752                        None,
753                        block_builder.mast_forest_builder_mut(),
754                    )?;
755                    let else_blk = self.compile_body(
756                        else_blk.iter(),
757                        proc_ctx,
758                        None,
759                        block_builder.mast_forest_builder_mut(),
760                    )?;
761
762                    let split_node_id =
763                        block_builder.mast_forest_builder_mut().ensure_split(then_blk, else_blk)?;
764                    if let Some(decorator_ids) = block_builder.drain_decorators() {
765                        block_builder
766                            .mast_forest_builder_mut()
767                            .append_before_enter(split_node_id, &decorator_ids)
768                    }
769
770                    // Add an assembly operation decorator to the if node in debug mode.
771                    if self.in_debug_mode() {
772                        let location = proc_ctx.source_manager().location(*span).ok();
773                        let context_name = proc_ctx.name().to_string();
774                        let num_cycles = 0;
775                        let op = "if.true".to_string();
776                        let should_break = false;
777                        let op =
778                            AssemblyOp::new(location, context_name, num_cycles, op, should_break);
779                        let decorator_id = block_builder
780                            .mast_forest_builder_mut()
781                            .ensure_decorator(Decorator::AsmOp(op))?;
782                        block_builder
783                            .mast_forest_builder_mut()
784                            .append_before_enter(split_node_id, &[decorator_id]);
785                    }
786
787                    body_node_ids.push(split_node_id);
788                },
789
790                Op::Repeat { count, body, .. } => {
791                    if let Some(basic_block_id) = block_builder.make_basic_block()? {
792                        body_node_ids.push(basic_block_id);
793                    }
794
795                    let repeat_node_id = self.compile_body(
796                        body.iter(),
797                        proc_ctx,
798                        None,
799                        block_builder.mast_forest_builder_mut(),
800                    )?;
801
802                    if let Some(decorator_ids) = block_builder.drain_decorators() {
803                        // Attach the decorators before the first instance of the repeated node
804                        let mut first_repeat_node =
805                            block_builder.mast_forest_builder_mut()[repeat_node_id].clone();
806                        first_repeat_node.append_before_enter(&decorator_ids);
807                        let first_repeat_node_id = block_builder
808                            .mast_forest_builder_mut()
809                            .ensure_node(first_repeat_node)?;
810
811                        body_node_ids.push(first_repeat_node_id);
812                        for _ in 0..(*count - 1) {
813                            body_node_ids.push(repeat_node_id);
814                        }
815                    } else {
816                        for _ in 0..*count {
817                            body_node_ids.push(repeat_node_id);
818                        }
819                    }
820                },
821
822                Op::While { body, span } => {
823                    if let Some(basic_block_id) = block_builder.make_basic_block()? {
824                        body_node_ids.push(basic_block_id);
825                    }
826
827                    let loop_node_id = {
828                        let loop_body_node_id = self.compile_body(
829                            body.iter(),
830                            proc_ctx,
831                            None,
832                            block_builder.mast_forest_builder_mut(),
833                        )?;
834                        block_builder.mast_forest_builder_mut().ensure_loop(loop_body_node_id)?
835                    };
836                    if let Some(decorator_ids) = block_builder.drain_decorators() {
837                        block_builder
838                            .mast_forest_builder_mut()
839                            .append_before_enter(loop_node_id, &decorator_ids)
840                    }
841
842                    // Add an assembly operation decorator to the loop node in debug mode.
843                    if self.in_debug_mode() {
844                        let location = proc_ctx.source_manager().location(*span).ok();
845                        let context_name = proc_ctx.name().to_string();
846                        let num_cycles = 0;
847                        let op = "while.true".to_string();
848                        let should_break = false;
849                        let op =
850                            AssemblyOp::new(location, context_name, num_cycles, op, should_break);
851                        let decorator_id = block_builder
852                            .mast_forest_builder_mut()
853                            .ensure_decorator(Decorator::AsmOp(op))?;
854                        block_builder
855                            .mast_forest_builder_mut()
856                            .append_before_enter(loop_node_id, &[decorator_id]);
857                    }
858
859                    body_node_ids.push(loop_node_id);
860                },
861            }
862        }
863
864        let maybe_post_decorators: Option<Vec<DecoratorId>> =
865            match block_builder.try_into_basic_block()? {
866                BasicBlockOrDecorators::BasicBlock(basic_block_id) => {
867                    body_node_ids.push(basic_block_id);
868                    None
869                },
870                BasicBlockOrDecorators::Decorators(decorator_ids) => {
871                    // the procedure body ends with a list of decorators
872                    Some(decorator_ids)
873                },
874                BasicBlockOrDecorators::Nothing => None,
875            };
876
877        let procedure_body_id = if body_node_ids.is_empty() {
878            // We cannot allow only decorators in a procedure body, since decorators don't change
879            // the MAST digest of a node. Hence, two empty procedures with different decorators
880            // would look the same to the `MastForestBuilder`.
881            if maybe_post_decorators.is_some() {
882                return Err(Report::new(
883                    RelatedLabel::error("invalid procedure")
884                        .with_labeled_span(
885                            proc_ctx.span(),
886                            "body must contain at least one instruction if it has decorators",
887                        )
888                        .with_source_file(
889                            proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
890                        ),
891                ));
892            }
893
894            mast_forest_builder.ensure_block(vec![Operation::Noop], None)?
895        } else {
896            mast_forest_builder.join_nodes(body_node_ids)?
897        };
898
899        // Make sure that any post decorators are added at the end of the procedure body
900        if let Some(post_decorator_ids) = maybe_post_decorators {
901            mast_forest_builder.append_after_exit(procedure_body_id, &post_decorator_ids);
902        }
903
904        Ok(procedure_body_id)
905    }
906
907    /// Resolves the specified target to the corresponding procedure root [`MastNodeId`].
908    ///
909    /// If no [`MastNodeId`] exists for that procedure root, we wrap the root in an
910    /// [`crate::mast::ExternalNode`], and return the resulting [`MastNodeId`].
911    pub(super) fn resolve_target(
912        &self,
913        kind: InvokeKind,
914        target: &InvocationTarget,
915        proc_ctx: &ProcedureContext,
916        mast_forest_builder: &mut MastForestBuilder,
917    ) -> Result<MastNodeId, Report> {
918        let caller = CallerInfo {
919            span: target.span(),
920            module: proc_ctx.id().module,
921            kind,
922        };
923        let resolved = self.linker.resolve_target(&caller, target)?;
924        match resolved {
925            ResolvedTarget::Phantom(mast_root) => self.ensure_valid_procedure_mast_root(
926                kind,
927                target.span(),
928                mast_root,
929                mast_forest_builder,
930            ),
931            ResolvedTarget::Exact { gid } | ResolvedTarget::Resolved { gid, .. } => {
932                match mast_forest_builder.get_procedure(gid) {
933                    Some(proc) => Ok(proc.body_node_id()),
934                    // We didn't find the procedure in our current MAST forest. We still need to
935                    // check if it exists in one of a library dependency.
936                    None => match self.linker.get_procedure_unsafe(gid) {
937                        ProcedureLink::Info(p) => self.ensure_valid_procedure_mast_root(
938                            kind,
939                            target.span(),
940                            p.digest,
941                            mast_forest_builder,
942                        ),
943                        ProcedureLink::Ast(_) => panic!(
944                            "AST procedure {gid:?} exists in the linker, but not in the MastForestBuilder"
945                        ),
946                    },
947                }
948            },
949        }
950    }
951
952    /// Verifies the validity of the MAST root as a procedure root hash, and adds it to the forest.
953    ///
954    /// If the root is present in the vendored MAST, its subtree is copied. Otherwise an
955    /// external node is added to the forest.
956    fn ensure_valid_procedure_mast_root(
957        &self,
958        kind: InvokeKind,
959        span: SourceSpan,
960        mast_root: Word,
961        mast_forest_builder: &mut MastForestBuilder,
962    ) -> Result<MastNodeId, Report> {
963        // Get the procedure from the assembler
964        let current_source_file = self.source_manager.get(span.source_id()).ok();
965
966        // If the procedure is cached and is a system call, ensure that the call is valid.
967        match mast_forest_builder.find_procedure_by_mast_root(&mast_root) {
968            Some(proc) if matches!(kind, InvokeKind::SysCall) => {
969                // Verify if this is a syscall, that the callee is a kernel procedure
970                //
971                // NOTE: The assembler is expected to know the full set of all kernel
972                // procedures at this point, so if we can't identify the callee as a
973                // kernel procedure, it is a definite error.
974                if !proc.visibility().is_syscall() {
975                    assert!(
976                        !proc.visibility().is_syscall(),
977                        "linker failed to validate syscall correctly: {}",
978                        Report::new(LinkerError::InvalidSysCallTarget {
979                            span,
980                            source_file: current_source_file,
981                            callee: proc.fully_qualified_name().clone().into(),
982                        })
983                    );
984                }
985                let maybe_kernel_path = proc.path();
986                let module = self.linker.find_module(maybe_kernel_path).unwrap_or_else(|| {
987                    panic!(
988                        "linker failed to validate syscall correctly: {}",
989                        Report::new(LinkerError::InvalidSysCallTarget {
990                            span,
991                            source_file: current_source_file.clone(),
992                            callee: proc.fully_qualified_name().clone().into(),
993                        })
994                    )
995                });
996                // Note: this module is guaranteed to be of AST variant, since we have the
997                // AST of a procedure contained in it (i.e. `proc`). Hence, it must be that
998                // the entire module is in AST representation as well.
999                if !module.unwrap_ast().is_kernel() {
1000                    panic!(
1001                        "linker failed to validate syscall correctly: {}",
1002                        Report::new(LinkerError::InvalidSysCallTarget {
1003                            span,
1004                            source_file: current_source_file.clone(),
1005                            callee: proc.fully_qualified_name().clone().into(),
1006                        })
1007                    )
1008                }
1009            },
1010            Some(_) | None => (),
1011        }
1012
1013        mast_forest_builder.ensure_external_link(mast_root)
1014    }
1015}
1016
1017// HELPERS
1018// ================================================================================================
1019
1020/// Contains a set of operations which need to be executed before and after a sequence of AST
1021/// nodes (i.e., code body).
1022pub(crate) struct BodyWrapper {
1023    pub prologue: Vec<Operation>,
1024    pub epilogue: Vec<Operation>,
1025}