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