Skip to main content

miden_assembly/
assembler.rs

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