miden_assembly_syntax/library/
mod.rs

1use alloc::{
2    collections::{BTreeMap, BTreeSet},
3    string::String,
4    sync::Arc,
5    vec::Vec,
6};
7
8use miden_core::{
9    AdviceMap, Kernel, Word,
10    mast::{MastForest, MastNodeId},
11    utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
12};
13
14use crate::ast::QualifiedProcedureName;
15
16mod error;
17mod module;
18mod namespace;
19mod path;
20
21pub use module::{ModuleInfo, ProcedureInfo};
22pub use semver::{Error as VersionError, Version};
23
24pub use self::{
25    error::LibraryError,
26    namespace::{LibraryNamespace, LibraryNamespaceError},
27    path::{LibraryPath, LibraryPathComponent, PathError},
28};
29
30// LIBRARY
31// ================================================================================================
32
33/// Represents a library where all modules were compiled into a [`MastForest`].
34///
35/// A library exports a set of one or more procedures. Currently, all exported procedures belong
36/// to the same top-level namespace.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct Library {
39    /// The content hash of this library, formed by hashing the roots of all exports in
40    /// lexicographical order (by digest, not procedure name)
41    digest: Word,
42    /// A map between procedure paths and the corresponding procedure roots in the MAST forest.
43    /// Multiple paths can map to the same root, and also, some roots may not be associated with
44    /// any paths.
45    ///
46    /// Note that we use `MastNodeId` as an identifier for procedures instead of MAST root, since 2
47    /// different procedures with the same MAST root can be different due to the decorators they
48    /// contain. However, note that `MastNodeId` is also not a unique identifier for procedures; if
49    /// the procedures have the same MAST root and decorators, they will have the same
50    /// `MastNodeId`.
51    exports: BTreeMap<QualifiedProcedureName, MastNodeId>,
52    /// The MAST forest underlying this library.
53    mast_forest: Arc<MastForest>,
54}
55
56impl AsRef<Library> for Library {
57    #[inline(always)]
58    fn as_ref(&self) -> &Library {
59        self
60    }
61}
62
63// ------------------------------------------------------------------------------------------------
64/// Constructors
65impl Library {
66    /// Constructs a new [`Library`] from the provided MAST forest and a set of exports.
67    ///
68    /// # Errors
69    /// Returns an error if the set of exports is empty.
70    /// Returns an error if any of the specified exports do not have a corresponding procedure root
71    /// in the provided MAST forest.
72    pub fn new(
73        mast_forest: Arc<MastForest>,
74        exports: BTreeMap<QualifiedProcedureName, MastNodeId>,
75    ) -> Result<Self, LibraryError> {
76        if exports.is_empty() {
77            return Err(LibraryError::NoExport);
78        }
79        for (fqn, &proc_body_id) in exports.iter() {
80            if !mast_forest.is_procedure_root(proc_body_id) {
81                return Err(LibraryError::NoProcedureRootForExport { procedure_path: fqn.clone() });
82            }
83        }
84
85        let digest = compute_content_hash(&exports, &mast_forest);
86
87        Ok(Self { digest, exports, mast_forest })
88    }
89
90    /// Produces a new library with the existing [`MastForest`] and where all key/values in the
91    /// provided advice map are added to the internal advice map.
92    pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
93        let mut mast_forest = (*self.mast_forest).clone();
94        mast_forest.advice_map_mut().extend(advice_map);
95        Self {
96            mast_forest: Arc::new(mast_forest),
97            ..self
98        }
99    }
100}
101
102// ------------------------------------------------------------------------------------------------
103/// Public accessors
104impl Library {
105    /// Returns the [Word] representing the content hash of this library
106    pub fn digest(&self) -> &Word {
107        &self.digest
108    }
109
110    /// Returns the fully qualified name of all procedures exported by the library.
111    pub fn exports(&self) -> impl Iterator<Item = &QualifiedProcedureName> {
112        self.exports.keys()
113    }
114
115    /// Returns the number of exports in this library.
116    pub fn num_exports(&self) -> usize {
117        self.exports.len()
118    }
119
120    /// Returns a MAST node ID associated with the specified exported procedure.
121    ///
122    /// # Panics
123    /// Panics if the specified procedure is not exported from this library.
124    pub fn get_export_node_id(&self, proc_name: &QualifiedProcedureName) -> MastNodeId {
125        *self.exports.get(proc_name).expect("procedure not exported from the library")
126    }
127
128    /// Returns true if the specified exported procedure is re-exported from a dependency.
129    pub fn is_reexport(&self, proc_name: &QualifiedProcedureName) -> bool {
130        self.exports
131            .get(proc_name)
132            .map(|&node_id| self.mast_forest[node_id].is_external())
133            .unwrap_or(false)
134    }
135
136    /// Returns a reference to the inner [`MastForest`].
137    pub fn mast_forest(&self) -> &Arc<MastForest> {
138        &self.mast_forest
139    }
140
141    /// Returns the digest of the procedure with the specified name, or `None` if it was not found
142    /// in the library or its library path is malformed.
143    pub fn get_procedure_root_by_name(
144        &self,
145        proc_name: impl TryInto<QualifiedProcedureName>,
146    ) -> Option<Word> {
147        if let Ok(qualified_proc_name) = proc_name.try_into() {
148            let node_id = self.exports.get(&qualified_proc_name);
149            node_id.map(|id| self.mast_forest()[*id].digest())
150        } else {
151            None
152        }
153    }
154}
155
156/// Conversions
157impl Library {
158    /// Returns an iterator over the module infos of the library.
159    pub fn module_infos(&self) -> impl Iterator<Item = ModuleInfo> {
160        let mut modules_by_path: BTreeMap<LibraryPath, ModuleInfo> = BTreeMap::new();
161
162        for (proc_name, &proc_root_node_id) in self.exports.iter() {
163            modules_by_path
164                .entry(proc_name.module.clone())
165                .and_modify(|compiled_module| {
166                    let proc_digest = self.mast_forest[proc_root_node_id].digest();
167                    compiled_module.add_procedure(proc_name.name.clone(), proc_digest);
168                })
169                .or_insert_with(|| {
170                    let mut module_info = ModuleInfo::new(proc_name.module.clone());
171
172                    let proc_digest = self.mast_forest[proc_root_node_id].digest();
173                    module_info.add_procedure(proc_name.name.clone(), proc_digest);
174
175                    module_info
176                });
177        }
178
179        modules_by_path.into_values()
180    }
181}
182
183impl Serializable for Library {
184    fn write_into<W: ByteWriter>(&self, target: &mut W) {
185        let Self { digest: _, exports, mast_forest } = self;
186
187        mast_forest.write_into(target);
188
189        target.write_usize(exports.len());
190        for (proc_name, proc_node_id) in exports {
191            proc_name.module.write_into(target);
192            proc_name.name.write_into(target);
193            target.write_u32(proc_node_id.as_u32());
194        }
195    }
196}
197
198impl Deserializable for Library {
199    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
200        let mast_forest = Arc::new(MastForest::read_from(source)?);
201
202        let num_exports = source.read_usize()?;
203        if num_exports == 0 {
204            return Err(DeserializationError::InvalidValue(String::from("No exported procedures")));
205        };
206        let mut exports = BTreeMap::new();
207        for _ in 0..num_exports {
208            let proc_module = source.read()?;
209            let proc_name = source.read()?;
210            let proc_name = QualifiedProcedureName::new(proc_module, proc_name);
211            let proc_node_id = MastNodeId::from_u32_safe(source.read_u32()?, &mast_forest)?;
212
213            exports.insert(proc_name, proc_node_id);
214        }
215
216        let digest = compute_content_hash(&exports, &mast_forest);
217
218        Ok(Self { digest, exports, mast_forest })
219    }
220}
221
222fn compute_content_hash(
223    exports: &BTreeMap<QualifiedProcedureName, MastNodeId>,
224    mast_forest: &MastForest,
225) -> Word {
226    let digests = BTreeSet::from_iter(exports.values().map(|&id| mast_forest[id].digest()));
227    digests
228        .into_iter()
229        .reduce(|a, b| miden_core::crypto::hash::Rpo256::merge(&[a, b]))
230        .unwrap()
231}
232
233#[cfg(feature = "std")]
234impl 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<std::path::Path>) -> std::io::Result<()> {
244        let path = path.as_ref();
245
246        if let Some(dir) = path.parent() {
247            std::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 = std::fs::File::create(path)?;
255            self.write_into(&mut file);
256            Ok(())
257        })
258        .map_err(|p| {
259            match p.downcast::<std::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    pub fn deserialize_from_file(
268        path: impl AsRef<std::path::Path>,
269    ) -> Result<Self, DeserializationError> {
270        use miden_core::utils::ReadAdapter;
271
272        let path = path.as_ref();
273        let mut file = std::fs::File::open(path).map_err(|err| {
274            DeserializationError::InvalidValue(format!(
275                "failed to open file at {}: {err}",
276                path.to_string_lossy()
277            ))
278        })?;
279        let mut adapter = ReadAdapter::new(&mut file);
280
281        Self::read_from(&mut adapter)
282    }
283}
284
285// KERNEL LIBRARY
286// ================================================================================================
287
288/// Represents a library containing a Miden VM kernel.
289///
290/// This differs from the regular [Library] as follows:
291/// - All exported procedures must be exported directly from the kernel namespace (i.e., `$kernel`).
292/// - There must be at least one exported procedure.
293/// - The number of exported procedures cannot exceed [Kernel::MAX_NUM_PROCEDURES] (i.e., 256).
294#[derive(Debug, Clone, PartialEq, Eq)]
295pub struct KernelLibrary {
296    kernel: Kernel,
297    kernel_info: ModuleInfo,
298    library: Library,
299}
300
301impl AsRef<Library> for KernelLibrary {
302    #[inline(always)]
303    fn as_ref(&self) -> &Library {
304        &self.library
305    }
306}
307
308impl KernelLibrary {
309    /// Returns the [Kernel] for this kernel library.
310    pub fn kernel(&self) -> &Kernel {
311        &self.kernel
312    }
313
314    /// Returns a reference to the inner [`MastForest`].
315    pub fn mast_forest(&self) -> &Arc<MastForest> {
316        self.library.mast_forest()
317    }
318
319    /// Destructures this kernel library into individual parts.
320    pub fn into_parts(self) -> (Kernel, ModuleInfo, Arc<MastForest>) {
321        (self.kernel, self.kernel_info, self.library.mast_forest)
322    }
323}
324
325impl TryFrom<Library> for KernelLibrary {
326    type Error = LibraryError;
327
328    fn try_from(library: Library) -> Result<Self, Self::Error> {
329        let kernel_path = LibraryPath::from(LibraryNamespace::Kernel);
330        let mut proc_digests = Vec::with_capacity(library.exports.len());
331
332        let mut kernel_module = ModuleInfo::new(kernel_path.clone());
333
334        for (proc_path, &proc_node_id) in library.exports.iter() {
335            // make sure all procedures are exported only from the kernel root
336            if proc_path.module != kernel_path {
337                return Err(LibraryError::InvalidKernelExport {
338                    procedure_path: proc_path.clone(),
339                });
340            }
341
342            let proc_digest = library.mast_forest[proc_node_id].digest();
343            proc_digests.push(proc_digest);
344            kernel_module.add_procedure(proc_path.name.clone(), proc_digest);
345        }
346
347        let kernel = Kernel::new(&proc_digests).map_err(LibraryError::KernelConversion)?;
348
349        Ok(Self {
350            kernel,
351            kernel_info: kernel_module,
352            library,
353        })
354    }
355}
356
357impl Serializable for KernelLibrary {
358    fn write_into<W: ByteWriter>(&self, target: &mut W) {
359        let Self { kernel: _, kernel_info: _, library } = self;
360
361        library.write_into(target);
362    }
363}
364
365impl Deserializable for KernelLibrary {
366    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
367        let library = Library::read_from(source)?;
368
369        Self::try_from(library).map_err(|err| {
370            DeserializationError::InvalidValue(format!(
371                "Failed to deserialize kernel library: {err}"
372            ))
373        })
374    }
375}
376
377#[cfg(feature = "std")]
378impl KernelLibrary {
379    /// Write the library to a target file
380    pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
381        self.library.write_to_file(path)
382    }
383}