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