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