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