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