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