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