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