miden_assembly/library/
mod.rs

1use alloc::{
2    collections::{BTreeMap, BTreeSet},
3    string::String,
4    sync::Arc,
5    vec::Vec,
6};
7
8use vm_core::{
9    AdviceMap, Kernel,
10    crypto::hash::RpoDigest,
11    mast::{MastForest, MastNodeId},
12    utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
13};
14
15use crate::ast::QualifiedProcedureName;
16
17mod error;
18mod module;
19mod namespace;
20mod path;
21mod version;
22
23pub use module::{ModuleInfo, ProcedureInfo};
24
25pub use self::{
26    error::LibraryError,
27    namespace::{LibraryNamespace, LibraryNamespaceError},
28    path::{LibraryPath, LibraryPathComponent, PathError},
29    version::{Version, VersionError},
30};
31
32#[cfg(test)]
33mod tests;
34
35// LIBRARY
36// ================================================================================================
37
38/// Represents a library where all modules were compiled into a [`MastForest`].
39///
40/// A library exports a set of one or more procedures. Currently, all exported procedures belong
41/// to the same top-level namespace.
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct Library {
44    /// The content hash of this library, formed by hashing the roots of all exports in
45    /// lexicographical order (by digest, not procedure name)
46    digest: RpoDigest,
47    /// A map between procedure paths and the corresponding procedure roots in the MAST forest.
48    /// Multiple paths can map to the same root, and also, some roots may not be associated with
49    /// any paths.
50    ///
51    /// Note that we use `MastNodeId` as an identifier for procedures instead of MAST root, since 2
52    /// different procedures with the same MAST root can be different due to the decorators they
53    /// contain. However, note that `MastNodeId` is also not a unique identifier for procedures; if
54    /// the procedures have the same MAST root and decorators, they will have the same
55    /// `MastNodeId`.
56    exports: BTreeMap<QualifiedProcedureName, MastNodeId>,
57    /// The MAST forest underlying this library.
58    mast_forest: Arc<MastForest>,
59}
60
61impl AsRef<Library> for Library {
62    #[inline(always)]
63    fn as_ref(&self) -> &Library {
64        self
65    }
66}
67
68// ------------------------------------------------------------------------------------------------
69/// Constructors
70impl Library {
71    /// Constructs a new [`Library`] from the provided MAST forest and a set of exports.
72    ///
73    /// # Errors
74    /// Returns an error if the set of exports is empty.
75    /// Returns an error if any of the specified exports do not have a corresponding procedure root
76    /// in the provided MAST forest.
77    pub fn new(
78        mast_forest: Arc<MastForest>,
79        exports: BTreeMap<QualifiedProcedureName, MastNodeId>,
80    ) -> Result<Self, LibraryError> {
81        if exports.is_empty() {
82            return Err(LibraryError::NoExport);
83        }
84        for (fqn, &proc_body_id) in exports.iter() {
85            if !mast_forest.is_procedure_root(proc_body_id) {
86                return Err(LibraryError::NoProcedureRootForExport { procedure_path: fqn.clone() });
87            }
88        }
89
90        let digest = compute_content_hash(&exports, &mast_forest);
91
92        Ok(Self { digest, exports, mast_forest })
93    }
94
95    /// Produces a new library with the existing [`MastForest`] and where all key/values in the  
96    /// provided advice map are added to the internal advice map.
97    pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
98        let mut mast_forest = (*self.mast_forest).clone();
99        mast_forest.advice_map_mut().extend(advice_map);
100        Self {
101            mast_forest: Arc::new(mast_forest),
102            ..self
103        }
104    }
105}
106
107// ------------------------------------------------------------------------------------------------
108/// Public accessors
109impl Library {
110    /// Returns the [RpoDigest] representing the content hash of this library
111    pub fn digest(&self) -> &RpoDigest {
112        &self.digest
113    }
114
115    /// Returns the fully qualified name of all procedures exported by the library.
116    pub fn exports(&self) -> impl Iterator<Item = &QualifiedProcedureName> {
117        self.exports.keys()
118    }
119
120    /// Returns the number of exports in this library.
121    pub fn num_exports(&self) -> usize {
122        self.exports.len()
123    }
124
125    /// Returns a MAST node ID associated with the specified exported procedure.
126    ///
127    /// # Panics
128    /// Panics if the specified procedure is not exported from this library.
129    pub fn get_export_node_id(&self, proc_name: &QualifiedProcedureName) -> MastNodeId {
130        *self.exports.get(proc_name).expect("procedure not exported from the library")
131    }
132
133    /// Returns true if the specified exported procedure is re-exported from a dependency.
134    pub fn is_reexport(&self, proc_name: &QualifiedProcedureName) -> bool {
135        self.exports
136            .get(proc_name)
137            .map(|&node_id| self.mast_forest[node_id].is_external())
138            .unwrap_or(false)
139    }
140
141    /// Returns a reference to the inner [`MastForest`].
142    pub fn mast_forest(&self) -> &Arc<MastForest> {
143        &self.mast_forest
144    }
145}
146
147/// Conversions
148impl Library {
149    /// Returns an iterator over the module infos of the library.
150    pub fn module_infos(&self) -> impl Iterator<Item = ModuleInfo> {
151        let mut modules_by_path: BTreeMap<LibraryPath, ModuleInfo> = BTreeMap::new();
152
153        for (proc_name, &proc_root_node_id) in self.exports.iter() {
154            modules_by_path
155                .entry(proc_name.module.clone())
156                .and_modify(|compiled_module| {
157                    let proc_digest = self.mast_forest[proc_root_node_id].digest();
158                    compiled_module.add_procedure(proc_name.name.clone(), proc_digest);
159                })
160                .or_insert_with(|| {
161                    let mut module_info = ModuleInfo::new(proc_name.module.clone());
162
163                    let proc_digest = self.mast_forest[proc_root_node_id].digest();
164                    module_info.add_procedure(proc_name.name.clone(), proc_digest);
165
166                    module_info
167                });
168        }
169
170        modules_by_path.into_values()
171    }
172}
173
174impl Serializable for Library {
175    fn write_into<W: ByteWriter>(&self, target: &mut W) {
176        let Self { digest: _, exports, mast_forest } = self;
177
178        mast_forest.write_into(target);
179
180        target.write_usize(exports.len());
181        for (proc_name, proc_node_id) in exports {
182            proc_name.module.write_into(target);
183            proc_name.name.write_into(target);
184            target.write_u32(proc_node_id.as_u32());
185        }
186    }
187}
188
189impl Deserializable for Library {
190    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
191        let mast_forest = Arc::new(MastForest::read_from(source)?);
192
193        let num_exports = source.read_usize()?;
194        if num_exports == 0 {
195            return Err(DeserializationError::InvalidValue(String::from("No exported procedures")));
196        };
197        let mut exports = BTreeMap::new();
198        for _ in 0..num_exports {
199            let proc_module = source.read()?;
200            let proc_name = source.read()?;
201            let proc_name = QualifiedProcedureName::new(proc_module, proc_name);
202            let proc_node_id = MastNodeId::from_u32_safe(source.read_u32()?, &mast_forest)?;
203
204            exports.insert(proc_name, proc_node_id);
205        }
206
207        let digest = compute_content_hash(&exports, &mast_forest);
208
209        Ok(Self { digest, exports, mast_forest })
210    }
211}
212
213fn compute_content_hash(
214    exports: &BTreeMap<QualifiedProcedureName, MastNodeId>,
215    mast_forest: &MastForest,
216) -> RpoDigest {
217    let digests = BTreeSet::from_iter(exports.values().map(|&id| mast_forest[id].digest()));
218    digests
219        .into_iter()
220        .reduce(|a, b| vm_core::crypto::hash::Rpo256::merge(&[a, b]))
221        .unwrap()
222}
223
224#[cfg(feature = "std")]
225mod use_std_library {
226    use std::{fs, io, path::Path};
227
228    use miette::Report;
229    use vm_core::utils::ReadAdapter;
230
231    use super::*;
232    use crate::Assembler;
233
234    impl Library {
235        /// File extension for the Assembly Library.
236        pub const LIBRARY_EXTENSION: &'static str = "masl";
237
238        /// Write the library to a target file
239        ///
240        /// NOTE: It is up to the caller to use the correct file extension, but there is no
241        /// specific requirement that the extension be set, or the same as
242        /// [`Self::LIBRARY_EXTENSION`].
243        pub fn write_to_file(&self, path: impl AsRef<Path>) -> io::Result<()> {
244            let path = path.as_ref();
245
246            if let Some(dir) = path.parent() {
247                fs::create_dir_all(dir)?;
248            }
249
250            // NOTE: We catch panics due to i/o errors here due to the fact that the ByteWriter
251            // trait does not provide fallible APIs, so WriteAdapter will panic if the underlying
252            // writes fail. This needs to be addressed in winterfell at some point
253            std::panic::catch_unwind(|| {
254                let mut file = fs::File::create(path)?;
255                self.write_into(&mut file);
256                Ok(())
257            })
258            .map_err(|p| {
259                match p.downcast::<io::Error>() {
260                    // SAFETY: It is guaranteed safe to read Box<std::io::Error>
261                    Ok(err) => unsafe { core::ptr::read(&*err) },
262                    Err(err) => std::panic::resume_unwind(err),
263                }
264            })?
265        }
266
267        /// Create a [Library] from a standard Miden Assembly project layout.
268        ///
269        /// The standard layout dictates that a given path is the root of a namespace, and the
270        /// directory hierarchy corresponds to the namespace hierarchy. A `.masm` file found in a
271        /// given subdirectory of the root, will be parsed with its [LibraryPath] set based on
272        /// where it resides in the directory structure.
273        ///
274        /// This function recursively parses the entire directory structure under `path`, ignoring
275        /// any files which do not have the `.masm` extension.
276        ///
277        /// For example, let's say I call this function like so:
278        ///
279        /// ```rust
280        /// use std::sync::Arc;
281        ///
282        /// use miden_assembly::{Assembler, Library, LibraryNamespace};
283        /// use vm_core::debuginfo::DefaultSourceManager;
284        ///
285        /// Library::from_dir(
286        ///     "~/masm/std",
287        ///     LibraryNamespace::new("std").unwrap(),
288        ///     Assembler::new(Arc::new(DefaultSourceManager::default())),
289        /// );
290        /// ```
291        ///
292        /// Here's how we would handle various files under this path:
293        ///
294        /// - ~/masm/std/sys.masm            -> Parsed as "std::sys"
295        /// - ~/masm/std/crypto/hash.masm    -> Parsed as "std::crypto::hash"
296        /// - ~/masm/std/math/u32.masm       -> Parsed as "std::math::u32"
297        /// - ~/masm/std/math/u64.masm       -> Parsed as "std::math::u64"
298        /// - ~/masm/std/math/README.md      -> Ignored
299        pub fn from_dir(
300            path: impl AsRef<Path>,
301            namespace: LibraryNamespace,
302            assembler: Assembler,
303        ) -> Result<Self, Report> {
304            let path = path.as_ref();
305
306            let src_manager = assembler.source_manager();
307            let modules = crate::parser::read_modules_from_dir(namespace, path, &src_manager)?;
308            assembler.assemble_library(modules)
309        }
310
311        pub fn deserialize_from_file(path: impl AsRef<Path>) -> Result<Self, DeserializationError> {
312            let path = path.as_ref();
313            let mut file = fs::File::open(path).map_err(|err| {
314                DeserializationError::InvalidValue(format!(
315                    "failed to open file at {}: {err}",
316                    path.to_string_lossy()
317                ))
318            })?;
319            let mut adapter = ReadAdapter::new(&mut file);
320
321            Self::read_from(&mut adapter)
322        }
323    }
324}
325
326// KERNEL LIBRARY
327// ================================================================================================
328
329/// Represents a library containing a Miden VM kernel.
330///
331/// This differs from the regular [Library] as follows:
332/// - All exported procedures must be exported directly from the kernel namespace (i.e., `#sys`).
333/// - There must be at least one exported procedure.
334/// - The number of exported procedures cannot exceed [Kernel::MAX_NUM_PROCEDURES] (i.e., 256).
335#[derive(Debug, Clone, PartialEq, Eq)]
336pub struct KernelLibrary {
337    kernel: Kernel,
338    kernel_info: ModuleInfo,
339    library: Library,
340}
341
342impl AsRef<Library> for KernelLibrary {
343    #[inline(always)]
344    fn as_ref(&self) -> &Library {
345        &self.library
346    }
347}
348
349impl KernelLibrary {
350    /// Returns the [Kernel] for this kernel library.
351    pub fn kernel(&self) -> &Kernel {
352        &self.kernel
353    }
354
355    /// Returns a reference to the inner [`MastForest`].
356    pub fn mast_forest(&self) -> &Arc<MastForest> {
357        self.library.mast_forest()
358    }
359
360    /// Destructures this kernel library into individual parts.
361    pub fn into_parts(self) -> (Kernel, ModuleInfo, Arc<MastForest>) {
362        (self.kernel, self.kernel_info, self.library.mast_forest)
363    }
364}
365
366impl TryFrom<Library> for KernelLibrary {
367    type Error = LibraryError;
368
369    fn try_from(library: Library) -> Result<Self, Self::Error> {
370        let kernel_path = LibraryPath::from(LibraryNamespace::Kernel);
371        let mut proc_digests = Vec::with_capacity(library.exports.len());
372
373        let mut kernel_module = ModuleInfo::new(kernel_path.clone());
374
375        for (proc_path, &proc_node_id) in library.exports.iter() {
376            // make sure all procedures are exported only from the kernel root
377            if proc_path.module != kernel_path {
378                return Err(LibraryError::InvalidKernelExport {
379                    procedure_path: proc_path.clone(),
380                });
381            }
382
383            let proc_digest = library.mast_forest[proc_node_id].digest();
384            proc_digests.push(proc_digest);
385            kernel_module.add_procedure(proc_path.name.clone(), proc_digest);
386        }
387
388        let kernel = Kernel::new(&proc_digests).map_err(LibraryError::KernelConversion)?;
389
390        Ok(Self {
391            kernel,
392            kernel_info: kernel_module,
393            library,
394        })
395    }
396}
397
398impl Serializable for KernelLibrary {
399    fn write_into<W: ByteWriter>(&self, target: &mut W) {
400        let Self { kernel: _, kernel_info: _, library } = self;
401
402        library.write_into(target);
403    }
404}
405
406impl Deserializable for KernelLibrary {
407    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
408        let library = Library::read_from(source)?;
409
410        Self::try_from(library).map_err(|err| {
411            DeserializationError::InvalidValue(format!(
412                "Failed to deserialize kernel library: {err}"
413            ))
414        })
415    }
416}
417
418#[cfg(feature = "std")]
419mod use_std_kernel {
420    use std::{io, path::Path};
421
422    use super::*;
423    use crate::{Assembler, diagnostics::Report};
424
425    impl KernelLibrary {
426        /// Write the library to a target file
427        pub fn write_to_file(&self, path: impl AsRef<Path>) -> io::Result<()> {
428            self.library.write_to_file(path)
429        }
430
431        /// Create a [KernelLibrary] from a standard Miden Assembly kernel project layout.
432        ///
433        /// The kernel library will export procedures defined by the module at `sys_module_path`.
434        /// If the optional `lib_dir` is provided, all modules under this directory will be
435        /// available from the kernel module under the `kernel` namespace. For example, if
436        /// `lib_dir` is set to "~/masm/lib", the files will be accessible in the kernel module as
437        /// follows:
438        ///
439        /// - ~/masm/lib/foo.masm        -> Can be imported as "kernel::foo"
440        /// - ~/masm/lib/bar/baz.masm    -> Can be imported as "kernel::bar::baz"
441        ///
442        /// Note: this is a temporary structure which will likely change once
443        /// <https://github.com/0xMiden/miden-vm/issues/1436> is implemented.
444        pub fn from_dir(
445            sys_module_path: impl AsRef<Path>,
446            lib_dir: Option<impl AsRef<Path>>,
447            mut assembler: Assembler,
448        ) -> Result<Self, Report> {
449            // if library directory is provided, add modules from this directory to the assembler
450            if let Some(lib_dir) = lib_dir {
451                let lib_dir = lib_dir.as_ref();
452                let namespace = LibraryNamespace::new("kernel").expect("invalid namespace");
453                assembler.add_modules_from_dir(namespace, lib_dir)?;
454            }
455
456            assembler.assemble_kernel(sys_module_path.as_ref())
457        }
458    }
459}