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