Skip to main content

miden_assembly/
assembler.rs

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