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