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