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        mast_forest_builder.set_emit_debug_info(self.emit_debug_info);
541        let mut exports = {
542            let mut exports = BTreeMap::new();
543
544            for module_idx in module_indices.iter().copied() {
545                let module = &self.linker[module_idx];
546
547                if let Some(advice_map) = module.advice_map() {
548                    mast_forest_builder.merge_advice_map(advice_map)?;
549                }
550
551                let module_kind = module.kind();
552                let module_path = module.path().clone();
553                for index in 0..module.symbols().len() {
554                    let index = ItemIndex::new(index);
555                    let gid = module_idx + index;
556
557                    let path: Arc<Path> = {
558                        let symbol = &self.linker[gid];
559                        if !symbol.visibility().is_public() {
560                            continue;
561                        }
562                        module_path.join(symbol.name()).into()
563                    };
564                    let export = self.export_symbol(
565                        gid,
566                        module_kind,
567                        path.clone(),
568                        &mut mast_forest_builder,
569                    )?;
570                    exports.insert(path, export);
571                }
572            }
573
574            exports
575        };
576
577        let (mast_forest, id_remappings) = mast_forest_builder.build();
578        for (_proc_name, export) in exports.iter_mut() {
579            match export {
580                LibraryExport::Procedure(export) => {
581                    if let Some(&new_node_id) = id_remappings.get(&export.node) {
582                        export.node = new_node_id;
583                    }
584                },
585                LibraryExport::Constant(_) | LibraryExport::Type(_) => (),
586            }
587        }
588
589        self.finish_library_product(mast_forest, exports, kind)
590    }
591
592    /// The purpose of this function is, for any given symbol in the set of modules being compiled
593    /// to a [Library], to generate a corresponding [LibraryExport] for that symbol.
594    ///
595    /// For procedures, this function is also responsible for compiling the procedure, and updating
596    /// the provided [MastForestBuilder] accordingly.
597    fn export_symbol(
598        &mut self,
599        gid: GlobalItemIndex,
600        module_kind: ModuleKind,
601        symbol_path: Arc<Path>,
602        mast_forest_builder: &mut MastForestBuilder,
603    ) -> Result<LibraryExport, Report> {
604        log::trace!(target: "assembler::export_symbol", "exporting {} {symbol_path}", match self.linker[gid].item() {
605            SymbolItem::Compiled(ItemInfo::Procedure(_)) => "compiled procedure",
606            SymbolItem::Compiled(ItemInfo::Constant(_)) => "compiled constant",
607            SymbolItem::Compiled(ItemInfo::Type(_)) => "compiled type",
608            SymbolItem::Procedure(_) => "procedure",
609            SymbolItem::Constant(_) => "constant",
610            SymbolItem::Type(_) => "type",
611            SymbolItem::Alias { .. } => "alias",
612        });
613        let mut cache = crate::linker::ResolverCache::default();
614        let export = match self.linker[gid].item() {
615            SymbolItem::Compiled(ItemInfo::Procedure(item)) => {
616                let resolved = match mast_forest_builder.get_procedure(gid) {
617                    Some(proc) => ResolvedProcedure {
618                        node: proc.body_node_id(),
619                        signature: proc.signature(),
620                    },
621                    // We didn't find the procedure in our current MAST forest. We still need to
622                    // check if it exists in one of a library dependency.
623                    None => {
624                        let node = self.ensure_valid_procedure_mast_root(
625                            InvokeKind::ProcRef,
626                            SourceSpan::UNKNOWN,
627                            item.digest,
628                            mast_forest_builder,
629                        )?;
630                        ResolvedProcedure { node, signature: item.signature.clone() }
631                    },
632                };
633                let digest = item.digest;
634                let ResolvedProcedure { node, signature } = resolved;
635                let attributes = item.attributes.clone();
636                let pctx = ProcedureContext::new(
637                    gid,
638                    /* is_program_entrypoint= */ false,
639                    symbol_path.clone(),
640                    Visibility::Public,
641                    signature.clone(),
642                    module_kind.is_kernel(),
643                    self.source_manager.clone(),
644                );
645
646                let procedure = pctx.into_procedure(digest, node);
647                self.linker.register_procedure_root(gid, digest)?;
648                mast_forest_builder.insert_procedure(gid, procedure)?;
649                LibraryExport::Procedure(ProcedureExport {
650                    node,
651                    path: symbol_path,
652                    signature: signature.map(|sig| (*sig).clone()),
653                    attributes,
654                })
655            },
656            SymbolItem::Compiled(ItemInfo::Constant(item)) => {
657                LibraryExport::Constant(ConstantExport {
658                    path: symbol_path,
659                    value: item.value.clone(),
660                })
661            },
662            SymbolItem::Compiled(ItemInfo::Type(item)) => {
663                LibraryExport::Type(TypeExport { path: symbol_path, ty: item.ty.clone() })
664            },
665            SymbolItem::Procedure(_) => {
666                self.compile_subgraph(SubgraphRoot::not_as_entrypoint(gid), mast_forest_builder)?;
667                let node = mast_forest_builder
668                    .get_procedure(gid)
669                    .expect("compilation succeeded but root not found in cache")
670                    .body_node_id();
671                let signature = self.linker.resolve_signature(gid)?;
672                let attributes = self.linker.resolve_attributes(gid)?;
673                LibraryExport::Procedure(ProcedureExport {
674                    node,
675                    path: symbol_path,
676                    signature: signature.map(Arc::unwrap_or_clone),
677                    attributes,
678                })
679            },
680            SymbolItem::Constant(item) => {
681                // Evaluate constant to a concrete value for export
682                let value = self.linker.const_eval(gid, &item.value, &mut cache)?;
683
684                LibraryExport::Constant(ConstantExport { path: symbol_path, value })
685            },
686            SymbolItem::Type(item) => {
687                let ty = self.linker.resolve_type(item.span(), gid)?;
688                LibraryExport::Type(TypeExport { path: symbol_path, ty })
689            },
690
691            SymbolItem::Alias { alias, resolved } => {
692                // All aliases should've been resolved by now
693                let resolved = resolved.get().unwrap_or_else(|| {
694                    panic!("unresolved alias {symbol_path} targeting: {}", alias.target())
695                });
696                return self.export_symbol(resolved, module_kind, symbol_path, mast_forest_builder);
697            },
698        };
699
700        Ok(export)
701    }
702
703    /// Compiles the provided module into a [`Program`]. The resulting program can be executed on
704    /// Miden VM.
705    ///
706    /// # Errors
707    ///
708    /// Returns an error if parsing or compilation of the specified program fails, or if the source
709    /// doesn't have an entrypoint.
710    pub fn assemble_program(self, source: impl Parse) -> Result<Program, Report> {
711        let options = ParseOptions {
712            kind: ModuleKind::Executable,
713            warnings_as_errors: self.warnings_as_errors,
714            path: Some(Path::exec_path().into()),
715        };
716
717        let program = source.parse_with_options(self.source_manager.clone(), options)?;
718        assert!(program.is_executable());
719
720        self.assemble_executable_modules(program, [])?.into_program()
721    }
722
723    pub(crate) fn assemble_library_modules(
724        mut self,
725        modules: impl IntoIterator<Item = Box<ast::Module>>,
726        kind: TargetType,
727    ) -> Result<AssemblyProduct, Report> {
728        let module_indices = self.linker.link(modules)?;
729        self.assemble_library_product(&module_indices, kind)
730    }
731
732    pub(crate) fn assemble_kernel_module(
733        mut self,
734        module: Box<ast::Module>,
735    ) -> Result<AssemblyProduct, Report> {
736        let module_indices = self.linker.link_kernel(module)?;
737        self.assemble_library_product(&module_indices, TargetType::Kernel)
738    }
739
740    pub(crate) fn assemble_executable_modules(
741        mut self,
742        program: Box<ast::Module>,
743        support_modules: impl IntoIterator<Item = Box<ast::Module>>,
744    ) -> Result<AssemblyProduct, Report> {
745        self.linker.link_modules(support_modules)?;
746
747        // Recompute graph with executable module, and start compiling
748        let module_index = self.linker.link([program])?[0];
749
750        // Find the executable entrypoint Note: it is safe to use `unwrap_ast()` here, since this is
751        // the module we just added, which is in AST representation.
752        let entrypoint = self.linker[module_index]
753            .symbols()
754            .position(|symbol| symbol.name().as_str() == Ident::MAIN)
755            .map(|index| module_index + ItemIndex::new(index))
756            .ok_or(SemanticAnalysisError::MissingEntrypoint)?;
757
758        // Compile the linked module graph rooted at the entrypoint
759        let staticlibs = self.linker.libraries().filter_map(|lib| {
760            if matches!(lib.linkage, Linkage::Static) {
761                Some(lib.mast.as_ref())
762            } else {
763                None
764            }
765        });
766        let mut mast_forest_builder = MastForestBuilder::new(staticlibs)?;
767        mast_forest_builder.set_emit_debug_info(self.emit_debug_info);
768
769        if let Some(advice_map) = self.linker[module_index].advice_map() {
770            mast_forest_builder.merge_advice_map(advice_map)?;
771        }
772
773        self.compile_subgraph(SubgraphRoot::with_entrypoint(entrypoint), &mut mast_forest_builder)?;
774        let entry_node_id = mast_forest_builder
775            .get_procedure(entrypoint)
776            .expect("compilation succeeded but root not found in cache")
777            .body_node_id();
778
779        // in case the node IDs changed, update the entrypoint ID to the new value
780        let (mast_forest, id_remappings) = mast_forest_builder.build();
781        let entry_node_id = *id_remappings.get(&entry_node_id).unwrap_or(&entry_node_id);
782
783        self.finish_program_product(mast_forest, entry_node_id, self.linker.kernel().clone())
784    }
785
786    fn finish_library_product(
787        &self,
788        mut mast_forest: miden_core::mast::MastForest,
789        exports: BTreeMap<Arc<Path>, LibraryExport>,
790        kind: TargetType,
791    ) -> Result<AssemblyProduct, Report> {
792        self.apply_debug_options(&mut mast_forest);
793
794        let library = Library::new(Arc::new(mast_forest), exports)?;
795        let manifest = PackageManifest::from_library(&library);
796        let debug_info = self.emit_debug_info.then(|| {
797            #[cfg_attr(not(feature = "std"), expect(unused_mut))]
798            let mut debug_info = self.debug_info.clone();
799            #[cfg(feature = "std")]
800            if let Some(trimmer) = self.source_path_trimmer() {
801                debug_info.trim_paths(&trimmer);
802            }
803            debug_info
804        });
805
806        Ok(AssemblyProduct::new(kind, Arc::new(library), None, manifest, debug_info))
807    }
808
809    fn finish_program_product(
810        &self,
811        mut mast_forest: miden_core::mast::MastForest,
812        entrypoint: MastNodeId,
813        kernel: Kernel,
814    ) -> Result<AssemblyProduct, Report> {
815        self.apply_debug_options(&mut mast_forest);
816
817        let mast = Arc::new(mast_forest);
818        let entry: Arc<Path> =
819            ast::Path::exec_path().join(ast::ProcedureName::MAIN_PROC_NAME).into();
820        let entrypoint = LibraryExport::Procedure(ProcedureExport {
821            node: entrypoint,
822            path: entry.clone(),
823            signature: None,
824            attributes: Default::default(),
825        });
826        let library = Arc::new(Library::new(mast, BTreeMap::from_iter([(entry, entrypoint)]))?);
827        let manifest = PackageManifest::from_library(&library);
828        let debug_info = self.emit_debug_info.then(|| {
829            #[cfg_attr(not(feature = "std"), expect(unused_mut))]
830            let mut debug_info = self.debug_info.clone();
831            #[cfg(feature = "std")]
832            if let Some(trimmer) = self.source_path_trimmer() {
833                debug_info.trim_paths(&trimmer);
834            }
835            debug_info
836        });
837
838        Ok(AssemblyProduct::new(
839            TargetType::Executable,
840            library,
841            Some(kernel),
842            manifest,
843            debug_info,
844        ))
845    }
846
847    fn apply_debug_options(&self, mast_forest: &mut miden_core::mast::MastForest) {
848        if !self.emit_debug_info {
849            mast_forest.clear_debug_info();
850            return;
851        }
852
853        if self.trim_paths {
854            #[cfg(feature = "std")]
855            if let Some(trimmer) = self.source_path_trimmer() {
856                mast_forest.debug_info_mut().rewrite_source_locations(
857                    |location| trimmer.trim_location(location),
858                    |location| trimmer.trim_file_line_col(location),
859                );
860            }
861        }
862    }
863
864    #[cfg(feature = "std")]
865    fn source_path_trimmer(&self) -> Option<debuginfo::SourcePathTrimmer> {
866        if !self.trim_paths {
867            return None;
868        }
869
870        std::env::current_dir().ok().map(debuginfo::SourcePathTrimmer::new)
871    }
872
873    /// Compile the uncompiled procedure in the linked module graph which are members of the
874    /// subgraph rooted at `root`, placing them in the MAST forest builder once compiled.
875    ///
876    /// Returns an error if any of the provided Miden Assembly is invalid.
877    fn compile_subgraph(
878        &mut self,
879        root: SubgraphRoot,
880        mast_forest_builder: &mut MastForestBuilder,
881    ) -> Result<(), Report> {
882        let mut worklist: Vec<GlobalItemIndex> = self
883            .linker
884            .topological_sort_from_root(root.proc_id)
885            .map_err(|cycle| {
886                let iter = cycle.into_node_ids();
887                let mut nodes = Vec::with_capacity(iter.len());
888                for node in iter {
889                    let module = self.linker[node.module].path();
890                    let proc = self.linker[node].name();
891                    nodes.push(format!("{}", module.join(proc)));
892                }
893                LinkerError::Cycle { nodes: nodes.into() }
894            })?
895            .into_iter()
896            .filter(|&gid| {
897                matches!(
898                    self.linker[gid].item(),
899                    SymbolItem::Procedure(_) | SymbolItem::Alias { .. }
900                )
901            })
902            .collect();
903
904        assert!(!worklist.is_empty());
905
906        self.process_graph_worklist(&mut worklist, &root, mast_forest_builder)
907    }
908
909    /// Compiles all procedures in the `worklist`.
910    fn process_graph_worklist(
911        &mut self,
912        worklist: &mut Vec<GlobalItemIndex>,
913        root: &SubgraphRoot,
914        mast_forest_builder: &mut MastForestBuilder,
915    ) -> Result<(), Report> {
916        // Process the topological ordering in reverse order (bottom-up), so that
917        // each procedure is compiled with all of its dependencies fully compiled
918        while let Some(procedure_gid) = worklist.pop() {
919            // If we have already compiled this procedure, do not recompile
920            if let Some(proc) = mast_forest_builder.get_procedure(procedure_gid) {
921                self.linker.register_procedure_root(procedure_gid, proc.mast_root())?;
922                continue;
923            }
924            // Fetch procedure metadata from the graph
925            let (module_kind, module_path) = {
926                let module = &self.linker[procedure_gid.module];
927                (module.kind(), module.path().clone())
928            };
929            match self.linker[procedure_gid].item() {
930                SymbolItem::Procedure(proc) => {
931                    let proc = proc.borrow();
932                    let num_locals = proc.num_locals();
933                    let path = Arc::<Path>::from(module_path.join(proc.name().as_str()));
934                    let signature = self.linker.resolve_signature(procedure_gid)?;
935                    let is_program_entrypoint =
936                        root.is_program_entrypoint && root.proc_id == procedure_gid;
937
938                    let pctx = ProcedureContext::new(
939                        procedure_gid,
940                        is_program_entrypoint,
941                        path.clone(),
942                        proc.visibility(),
943                        signature.clone(),
944                        module_kind.is_kernel(),
945                        self.source_manager.clone(),
946                    )
947                    .with_num_locals(num_locals)
948                    .with_span(proc.span());
949
950                    // Compile this procedure
951                    let procedure = self.compile_procedure(pctx, mast_forest_builder)?;
952                    // TODO: if a re-exported procedure with the same MAST root had been previously
953                    // added to the builder, this will result in unreachable nodes added to the
954                    // MAST forest. This is because while we won't insert a duplicate node for the
955                    // procedure body node itself, all nodes that make up the procedure body would
956                    // be added to the forest.
957
958                    // Record the debug info for this procedure
959                    self.debug_info
960                        .register_procedure_debug_info(&procedure, self.source_manager.as_ref())?;
961
962                    // Cache the compiled procedure
963                    drop(proc);
964                    self.linker.register_procedure_root(procedure_gid, procedure.mast_root())?;
965                    mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
966                },
967                SymbolItem::Alias { alias, resolved } => {
968                    let procedure_gid = resolved.get().expect("resolved alias");
969                    match self.linker[procedure_gid].item() {
970                        SymbolItem::Procedure(_) | SymbolItem::Compiled(ItemInfo::Procedure(_)) => {
971                        },
972                        SymbolItem::Constant(_) | SymbolItem::Type(_) | SymbolItem::Compiled(_) => {
973                            continue;
974                        },
975                        // A resolved alias will always refer to a non-alias item, this is because
976                        // when aliases are resolved, they are resolved recursively. Had the alias
977                        // chain been cyclical, we would have raised an error already.
978                        SymbolItem::Alias { .. } => unreachable!(),
979                    }
980                    let path = module_path.join(alias.name().as_str()).into();
981                    // A program entrypoint is never an alias
982                    let is_program_entrypoint = false;
983                    let mut pctx = ProcedureContext::new(
984                        procedure_gid,
985                        is_program_entrypoint,
986                        path,
987                        ast::Visibility::Public,
988                        None,
989                        module_kind.is_kernel(),
990                        self.source_manager.clone(),
991                    )
992                    .with_span(alias.span());
993
994                    // We must resolve aliases at this point to their real definition, in order to
995                    // know whether we need to emit a MAST node for a foreign procedure item. If
996                    // the aliased item is not a procedure, we can ignore the alias entirely.
997                    let Some(ResolvedProcedure { node: proc_node_id, signature }) = self
998                        .resolve_target(
999                            InvokeKind::ProcRef,
1000                            &alias.target().into(),
1001                            procedure_gid,
1002                            mast_forest_builder,
1003                        )?
1004                    else {
1005                        continue;
1006                    };
1007
1008                    pctx.set_signature(signature);
1009
1010                    let proc_mast_root =
1011                        mast_forest_builder.get_mast_node(proc_node_id).unwrap().digest();
1012
1013                    let procedure = pctx.into_procedure(proc_mast_root, proc_node_id);
1014
1015                    // Make the MAST root available to all dependents
1016                    self.linker.register_procedure_root(procedure_gid, proc_mast_root)?;
1017                    mast_forest_builder.insert_procedure(procedure_gid, procedure)?;
1018                },
1019                SymbolItem::Compiled(_) | SymbolItem::Constant(_) | SymbolItem::Type(_) => {
1020                    // There is nothing to do for other items that might have edges in the graph
1021                },
1022            }
1023        }
1024
1025        Ok(())
1026    }
1027
1028    /// Compiles a single Miden Assembly procedure to its MAST representation.
1029    fn compile_procedure(
1030        &self,
1031        mut proc_ctx: ProcedureContext,
1032        mast_forest_builder: &mut MastForestBuilder,
1033    ) -> Result<Procedure, Report> {
1034        // Make sure the current procedure context is available during codegen
1035        let gid = proc_ctx.id();
1036
1037        let num_locals = proc_ctx.num_locals();
1038
1039        let proc = match self.linker[gid].item() {
1040            SymbolItem::Procedure(proc) => proc.borrow(),
1041            _ => panic!("expected item to be a procedure AST"),
1042        };
1043        let body_wrapper = if proc_ctx.is_program_entrypoint() {
1044            assert!(num_locals == 0, "program entrypoint cannot have locals");
1045
1046            Some(BodyWrapper {
1047                prologue: fmp_initialization_sequence(),
1048                epilogue: Vec::new(),
1049            })
1050        } else if num_locals > 0 {
1051            Some(BodyWrapper {
1052                prologue: fmp_start_frame_sequence(num_locals),
1053                epilogue: fmp_end_frame_sequence(num_locals),
1054            })
1055        } else {
1056            None
1057        };
1058
1059        let proc_body_id =
1060            self.compile_body(proc.iter(), &mut proc_ctx, body_wrapper, mast_forest_builder, 0)?;
1061
1062        let proc_body_node = mast_forest_builder
1063            .get_mast_node(proc_body_id)
1064            .expect("no MAST node for compiled procedure");
1065        Ok(proc_ctx.into_procedure(proc_body_node.digest(), proc_body_id))
1066    }
1067
1068    /// Creates an assembly operation decorator for control flow nodes.
1069    fn create_asmop_decorator(
1070        &self,
1071        span: &SourceSpan,
1072        op_name: &str,
1073        proc_ctx: &ProcedureContext,
1074    ) -> AssemblyOp {
1075        let location = proc_ctx.source_manager().location(*span).ok();
1076        let context_name = proc_ctx.path().to_string();
1077        let num_cycles = 0;
1078        AssemblyOp::new(location, context_name, num_cycles, op_name.to_string())
1079    }
1080
1081    fn compile_body<'a, I>(
1082        &self,
1083        body: I,
1084        proc_ctx: &mut ProcedureContext,
1085        wrapper: Option<BodyWrapper>,
1086        mast_forest_builder: &mut MastForestBuilder,
1087        nesting_depth: usize,
1088    ) -> Result<MastNodeId, Report>
1089    where
1090        I: Iterator<Item = &'a ast::Op>,
1091    {
1092        use ast::Op;
1093
1094        let mut body_node_ids: Vec<MastNodeId> = Vec::new();
1095        let mut block_builder = BasicBlockBuilder::new(wrapper, mast_forest_builder);
1096
1097        for op in body {
1098            match op {
1099                Op::Inst(inst) => {
1100                    if let Some(node_id) =
1101                        self.compile_instruction(inst, &mut block_builder, proc_ctx)?
1102                    {
1103                        if let Some(basic_block_id) = block_builder.make_basic_block()? {
1104                            body_node_ids.push(basic_block_id);
1105                        } else if let Some(decorator_ids) = block_builder.drain_decorators() {
1106                            block_builder
1107                                .mast_forest_builder_mut()
1108                                .append_before_enter(node_id, decorator_ids)
1109                                .into_diagnostic()?;
1110                        }
1111
1112                        body_node_ids.push(node_id);
1113                    }
1114                },
1115
1116                Op::If { then_blk, else_blk, span } => {
1117                    if let Some(basic_block_id) = block_builder.make_basic_block()? {
1118                        body_node_ids.push(basic_block_id);
1119                    }
1120
1121                    let next_depth = nesting_depth + 1;
1122                    if next_depth > MAX_CONTROL_FLOW_NESTING {
1123                        return Err(Report::new(AssemblerError::ControlFlowNestingDepthExceeded {
1124                            span: *span,
1125                            source_file: proc_ctx.source_manager().get(span.source_id()).ok(),
1126                            max_depth: MAX_CONTROL_FLOW_NESTING,
1127                        }));
1128                    }
1129
1130                    let then_blk = self.compile_body(
1131                        then_blk.iter(),
1132                        proc_ctx,
1133                        None,
1134                        block_builder.mast_forest_builder_mut(),
1135                        next_depth,
1136                    )?;
1137                    let else_blk = self.compile_body(
1138                        else_blk.iter(),
1139                        proc_ctx,
1140                        None,
1141                        block_builder.mast_forest_builder_mut(),
1142                        next_depth,
1143                    )?;
1144
1145                    let asm_op = self.create_asmop_decorator(span, "if.true", proc_ctx);
1146                    let mut split_builder = SplitNodeBuilder::new([then_blk, else_blk]);
1147                    if let Some(decorator_ids) = block_builder.drain_decorators() {
1148                        split_builder.append_before_enter(decorator_ids);
1149                    }
1150
1151                    let split_node_id = block_builder
1152                        .mast_forest_builder_mut()
1153                        .ensure_node_with_asm_op(split_builder, asm_op)?;
1154
1155                    body_node_ids.push(split_node_id);
1156                },
1157
1158                Op::Repeat { count, body, span } => {
1159                    if let Some(basic_block_id) = block_builder.make_basic_block()? {
1160                        body_node_ids.push(basic_block_id);
1161                    }
1162
1163                    let next_depth = nesting_depth + 1;
1164                    if next_depth > MAX_CONTROL_FLOW_NESTING {
1165                        return Err(Report::new(AssemblerError::ControlFlowNestingDepthExceeded {
1166                            span: *span,
1167                            source_file: proc_ctx.source_manager().get(span.source_id()).ok(),
1168                            max_depth: MAX_CONTROL_FLOW_NESTING,
1169                        }));
1170                    }
1171
1172                    let repeat_node_id = self.compile_body(
1173                        body.iter(),
1174                        proc_ctx,
1175                        None,
1176                        block_builder.mast_forest_builder_mut(),
1177                        next_depth,
1178                    )?;
1179
1180                    let iteration_count = (*count).expect_value();
1181                    if iteration_count == 0 {
1182                        return Err(RelatedLabel::error("invalid repeat count")
1183                            .with_help("repeat count must be greater than 0")
1184                            .with_labeled_span(count.span(), "repeat count must be at least 1")
1185                            .with_source_file(
1186                                proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
1187                            )
1188                            .into());
1189                    }
1190                    if iteration_count > MAX_REPEAT_COUNT {
1191                        return Err(RelatedLabel::error("invalid repeat count")
1192                            .with_help(format!(
1193                                "repeat count must be less than or equal to {MAX_REPEAT_COUNT}",
1194                            ))
1195                            .with_labeled_span(
1196                                count.span(),
1197                                format!("repeat count exceeds {MAX_REPEAT_COUNT}"),
1198                            )
1199                            .with_source_file(
1200                                proc_ctx.source_manager().get(proc_ctx.span().source_id()).ok(),
1201                            )
1202                            .into());
1203                    }
1204
1205                    if let Some(decorator_ids) = block_builder.drain_decorators() {
1206                        // Attach decorators before the first iteration. We must carry the
1207                        // original node's external metadata into the dedup fingerprint,
1208                        // otherwise structurally identical nodes with different source mappings
1209                        // can alias.
1210                        let first_repeat_builder = block_builder.mast_forest_builder()
1211                            [repeat_node_id]
1212                            .clone()
1213                            .to_builder(block_builder.mast_forest_builder().mast_forest())
1214                            .with_before_enter(decorator_ids);
1215                        let first_repeat_node_id = block_builder
1216                            .mast_forest_builder_mut()
1217                            .ensure_node_preserving_debug_vars(
1218                                first_repeat_builder,
1219                                repeat_node_id,
1220                            )?;
1221
1222                        body_node_ids.push(first_repeat_node_id);
1223                        let remaining_iterations =
1224                            iteration_count.checked_sub(1).ok_or_else(|| {
1225                                Report::new(
1226                                    RelatedLabel::error("invalid repeat count")
1227                                        .with_help("repeat count must be greater than 0")
1228                                        .with_labeled_span(
1229                                            count.span(),
1230                                            "repeat count must be at least 1",
1231                                        )
1232                                        .with_source_file(
1233                                            proc_ctx
1234                                                .source_manager()
1235                                                .get(proc_ctx.span().source_id())
1236                                                .ok(),
1237                                        ),
1238                                )
1239                            })?;
1240                        for _ in 0..remaining_iterations {
1241                            body_node_ids.push(repeat_node_id);
1242                        }
1243                    } else {
1244                        for _ in 0..iteration_count {
1245                            body_node_ids.push(repeat_node_id);
1246                        }
1247                    }
1248                },
1249
1250                Op::While { body, span } => {
1251                    if let Some(basic_block_id) = block_builder.make_basic_block()? {
1252                        body_node_ids.push(basic_block_id);
1253                    }
1254
1255                    let next_depth = nesting_depth + 1;
1256                    if next_depth > MAX_CONTROL_FLOW_NESTING {
1257                        return Err(Report::new(AssemblerError::ControlFlowNestingDepthExceeded {
1258                            span: *span,
1259                            source_file: proc_ctx.source_manager().get(span.source_id()).ok(),
1260                            max_depth: MAX_CONTROL_FLOW_NESTING,
1261                        }));
1262                    }
1263
1264                    let loop_body_node_id = self.compile_body(
1265                        body.iter(),
1266                        proc_ctx,
1267                        None,
1268                        block_builder.mast_forest_builder_mut(),
1269                        next_depth,
1270                    )?;
1271                    let mut loop_builder = LoopNodeBuilder::new(loop_body_node_id);
1272                    if let Some(decorator_ids) = block_builder.drain_decorators() {
1273                        loop_builder.append_before_enter(decorator_ids);
1274                    }
1275
1276                    let asm_op = self.create_asmop_decorator(span, "while.true", proc_ctx);
1277                    let loop_node_id = block_builder
1278                        .mast_forest_builder_mut()
1279                        .ensure_node_with_asm_op(loop_builder, 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                vec![],
1323            )?
1324        } else {
1325            let asm_op = self.create_asmop_decorator(&proc_ctx.span(), "begin", proc_ctx);
1326            mast_forest_builder.join_nodes(body_node_ids, Some(asm_op))?
1327        };
1328
1329        // Make sure that any post decorators are added at the end of the procedure body
1330        if let Some(post_decorator_ids) = maybe_post_decorators {
1331            mast_forest_builder
1332                .append_after_exit(procedure_body_id, post_decorator_ids)
1333                .into_diagnostic()?;
1334        }
1335
1336        Ok(procedure_body_id)
1337    }
1338
1339    /// Resolves the specified target to the corresponding procedure root [`MastNodeId`].
1340    ///
1341    /// If the resolved target is a non-procedure item, this returns `Ok(None)`.
1342    ///
1343    /// If no [`MastNodeId`] exists for that procedure root, we wrap the root in an
1344    /// [`crate::mast::ExternalNode`], and return the resulting [`MastNodeId`].
1345    pub(super) fn resolve_target(
1346        &self,
1347        kind: InvokeKind,
1348        target: &InvocationTarget,
1349        caller_id: GlobalItemIndex,
1350        mast_forest_builder: &mut MastForestBuilder,
1351    ) -> Result<Option<ResolvedProcedure>, Report> {
1352        let caller = SymbolResolutionContext {
1353            span: target.span(),
1354            module: caller_id.module,
1355            kind: Some(kind),
1356        };
1357        let resolved = self.linker.resolve_invoke_target(&caller, target)?;
1358        match resolved {
1359            SymbolResolution::MastRoot(mast_root) => {
1360                let node = self.ensure_valid_procedure_mast_root(
1361                    kind,
1362                    target.span(),
1363                    mast_root.into_inner(),
1364                    mast_forest_builder,
1365                )?;
1366                Ok(Some(ResolvedProcedure { node, signature: None }))
1367            },
1368            SymbolResolution::Exact { gid, .. } => {
1369                match mast_forest_builder.get_procedure(gid) {
1370                    Some(proc) => Ok(Some(ResolvedProcedure {
1371                        node: proc.body_node_id(),
1372                        signature: proc.signature(),
1373                    })),
1374                    // We didn't find the procedure in our current MAST forest. We still need to
1375                    // check if it exists in one of a library dependency.
1376                    None => match self.linker[gid].item() {
1377                        SymbolItem::Compiled(ItemInfo::Procedure(p)) => {
1378                            let node = self.ensure_valid_procedure_mast_root(
1379                                kind,
1380                                target.span(),
1381                                p.digest,
1382                                mast_forest_builder,
1383                            )?;
1384                            Ok(Some(ResolvedProcedure { node, signature: p.signature.clone() }))
1385                        },
1386                        SymbolItem::Procedure(_) => panic!(
1387                            "AST procedure {gid:?} exists in the linker, but not in the MastForestBuilder"
1388                        ),
1389                        SymbolItem::Alias { .. } => {
1390                            unreachable!("unexpected reference to ast alias item from {gid:?}")
1391                        },
1392                        SymbolItem::Compiled(_) | SymbolItem::Type(_) | SymbolItem::Constant(_) => {
1393                            Ok(None)
1394                        },
1395                    },
1396                }
1397            },
1398            SymbolResolution::Module { .. }
1399            | SymbolResolution::External(_)
1400            | SymbolResolution::Local(_) => unreachable!(),
1401        }
1402    }
1403
1404    /// Verifies the validity of the MAST root as a procedure root hash, and adds it to the forest.
1405    ///
1406    /// If the root is present in the vendored MAST, its subtree is copied. Otherwise an
1407    /// external node is added to the forest.
1408    fn ensure_valid_procedure_mast_root(
1409        &self,
1410        kind: InvokeKind,
1411        span: SourceSpan,
1412        mast_root: Word,
1413        mast_forest_builder: &mut MastForestBuilder,
1414    ) -> Result<MastNodeId, Report> {
1415        // Get the procedure from the assembler
1416        let current_source_file = self.source_manager.get(span.source_id()).ok();
1417
1418        if matches!(kind, InvokeKind::SysCall) && self.linker.has_nonempty_kernel() {
1419            // NOTE: The assembler is expected to know the full set of all kernel
1420            // procedures at this point, so if the digest is not present in the kernel,
1421            // it is a definite error.
1422            if !self.linker.kernel().contains_proc(mast_root) {
1423                let callee = mast_forest_builder
1424                    .find_procedure_by_mast_root(&mast_root)
1425                    .map(|proc| proc.path().clone())
1426                    .unwrap_or_else(|| {
1427                        let digest_path = format!("{mast_root}");
1428                        Arc::<Path>::from(Path::new(&digest_path))
1429                    });
1430                return Err(Report::new(LinkerError::InvalidSysCallTarget {
1431                    span,
1432                    source_file: current_source_file,
1433                    callee,
1434                }));
1435            }
1436        }
1437
1438        mast_forest_builder.ensure_external_link(mast_root)
1439    }
1440}
1441
1442// HELPERS
1443// ================================================================================================
1444
1445/// Information about the root of a subgraph to be compiled.
1446///
1447/// `is_program_entrypoint` is true if the root procedure is the entrypoint of an executable
1448/// program.
1449struct SubgraphRoot {
1450    proc_id: GlobalItemIndex,
1451    is_program_entrypoint: bool,
1452}
1453
1454impl SubgraphRoot {
1455    fn with_entrypoint(proc_id: GlobalItemIndex) -> Self {
1456        Self { proc_id, is_program_entrypoint: true }
1457    }
1458
1459    fn not_as_entrypoint(proc_id: GlobalItemIndex) -> Self {
1460        Self { proc_id, is_program_entrypoint: false }
1461    }
1462}
1463
1464/// Contains a set of operations which need to be executed before and after a sequence of AST
1465/// nodes (i.e., code body).
1466pub(crate) struct BodyWrapper {
1467    pub prologue: Vec<Operation>,
1468    pub epilogue: Vec<Operation>,
1469}
1470
1471pub(super) struct ResolvedProcedure {
1472    pub node: MastNodeId,
1473    pub signature: Option<Arc<FunctionType>>,
1474}