Skip to main content

miden_assembly_syntax/library/
mod.rs

1use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec};
2
3use miden_core::{
4    Word,
5    advice::AdviceMap,
6    mast::{MastForest, MastNodeExt, MastNodeId},
7    program::Kernel,
8    serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
9};
10use midenc_hir_type::{FunctionType, Type};
11#[cfg(feature = "arbitrary")]
12use proptest::prelude::*;
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16use crate::ast::{AttributeSet, Ident, Path, PathBuf, ProcedureName};
17
18mod error;
19mod module;
20
21pub use module::{ConstantInfo, ItemInfo, ModuleInfo, ProcedureInfo, TypeInfo};
22pub use semver::{Error as VersionError, Version};
23
24pub use self::error::LibraryError;
25
26// LIBRARY EXPORT
27// ================================================================================================
28
29/// Metadata about a procedure exported by the interface of a [Library]
30#[derive(Debug, Clone, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
33pub enum LibraryExport {
34    Procedure(ProcedureExport),
35    Constant(ConstantExport),
36    Type(TypeExport),
37}
38
39impl LibraryExport {
40    pub fn path(&self) -> Arc<Path> {
41        match self {
42            Self::Procedure(export) => export.path.clone(),
43            Self::Constant(export) => export.path.clone(),
44            Self::Type(export) => export.path.clone(),
45        }
46    }
47
48    pub fn as_procedure(&self) -> Option<&ProcedureExport> {
49        match self {
50            Self::Procedure(proc) => Some(proc),
51            Self::Constant(_) | Self::Type(_) => None,
52        }
53    }
54
55    pub fn unwrap_procedure(&self) -> &ProcedureExport {
56        match self {
57            Self::Procedure(proc) => proc,
58            Self::Constant(_) | Self::Type(_) => panic!("expected export to be a procedure"),
59        }
60    }
61}
62
63impl From<ProcedureExport> for LibraryExport {
64    fn from(value: ProcedureExport) -> Self {
65        Self::Procedure(value)
66    }
67}
68
69impl From<ConstantExport> for LibraryExport {
70    fn from(value: ConstantExport) -> Self {
71        Self::Constant(value)
72    }
73}
74
75impl From<TypeExport> for LibraryExport {
76    fn from(value: TypeExport) -> Self {
77        Self::Type(value)
78    }
79}
80
81#[cfg(feature = "arbitrary")]
82impl Arbitrary for LibraryExport {
83    type Parameters = ();
84
85    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
86        use proptest::{arbitrary::any, prop_oneof, strategy::Strategy};
87
88        prop_oneof![
89            any::<ProcedureExport>().prop_map(Self::Procedure),
90            any::<ConstantExport>().prop_map(Self::Constant),
91            any::<TypeExport>().prop_map(Self::Type),
92        ]
93        .boxed()
94    }
95
96    type Strategy = BoxedStrategy<Self>;
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
100#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
101#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
102pub struct ProcedureExport {
103    /// The id of the MAST root node of the exported procedure
104    pub node: MastNodeId,
105    /// The fully-qualified path of the exported procedure
106    #[cfg_attr(feature = "serde", serde(with = "crate::ast::path"))]
107    pub path: Arc<Path>,
108    /// The type signature of the exported procedure, if known
109    #[cfg_attr(feature = "serde", serde(default))]
110    pub signature: Option<FunctionType>,
111    #[cfg_attr(feature = "serde", serde(default))]
112    pub attributes: AttributeSet,
113}
114
115impl ProcedureExport {
116    /// Create a new [ProcedureExport] representing the export of `node` with `path`
117    pub fn new(node: MastNodeId, path: Arc<Path>) -> Self {
118        Self {
119            node,
120            path,
121            signature: None,
122            attributes: Default::default(),
123        }
124    }
125
126    /// Specify the type signature and ABI of this export
127    pub fn with_signature(mut self, signature: FunctionType) -> Self {
128        self.signature = Some(signature);
129        self
130    }
131
132    /// Specify the set of attributes attached to this export
133    pub fn with_attributes(mut self, attrs: AttributeSet) -> Self {
134        self.attributes = attrs;
135        self
136    }
137}
138
139#[cfg(feature = "arbitrary")]
140impl Arbitrary for ProcedureExport {
141    type Parameters = ();
142
143    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
144        use proptest::collection::vec as prop_vec;
145        use smallvec::SmallVec;
146
147        // Generate a small set of simple types for params/results to keep strategies fast/stable
148        let simple_type = prop_oneof![Just(Type::Felt), Just(Type::U32), Just(Type::U64),];
149
150        // Small vectors of params/results
151        let params = prop_vec(simple_type.clone(), 0..=4);
152        let results = prop_vec(simple_type, 0..=2);
153
154        // Use Fast ABI for roundtrip coverage
155        let abi = Just(midenc_hir_type::CallConv::Fast);
156
157        // Option<FunctionType>
158        let signature =
159            prop::option::of((abi, params, results).prop_map(|(abi, params_vec, results_vec)| {
160                let params = SmallVec::<[Type; 4]>::from_vec(params_vec);
161                let results = SmallVec::<[Type; 1]>::from_vec(results_vec);
162                FunctionType { abi, params, results }
163            }));
164
165        let nid = any::<MastNodeId>();
166        let name = any::<crate::ast::QualifiedProcedureName>();
167        (nid, name, signature)
168            .prop_map(|(nodeid, procname, signature)| Self {
169                node: nodeid,
170                path: procname.to_path_buf().into(),
171                signature,
172                attributes: Default::default(),
173            })
174            .boxed()
175    }
176
177    type Strategy = BoxedStrategy<Self>;
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
181#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
182#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
183#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
184pub struct ConstantExport {
185    /// The fully-qualified path of the exported constant
186    #[cfg_attr(feature = "serde", serde(with = "crate::ast::path"))]
187    #[cfg_attr(
188        feature = "arbitrary",
189        proptest(strategy = "crate::arbitrary::path::constant_path_random_length(1)")
190    )]
191    pub path: Arc<Path>,
192    /// The constant-folded AST representing the value of this constant
193    pub value: crate::ast::ConstantValue,
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
197#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
198#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
199pub struct TypeExport {
200    /// The fully-qualified path of the exported type declaration
201    #[cfg_attr(feature = "serde", serde(with = "crate::ast::path"))]
202    pub path: Arc<Path>,
203    /// The type bound to `name`
204    pub ty: crate::ast::types::Type,
205}
206
207#[cfg(feature = "arbitrary")]
208impl Arbitrary for TypeExport {
209    type Parameters = ();
210
211    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
212        use proptest::strategy::{Just, Strategy};
213        let path = crate::arbitrary::path::user_defined_type_path_random_length(1);
214        let ty = Just(crate::ast::types::Type::Felt);
215
216        (path, ty).prop_map(|(path, ty)| Self { path, ty }).boxed()
217    }
218
219    type Strategy = BoxedStrategy<Self>;
220}
221
222// LIBRARY
223// ================================================================================================
224
225/// Represents a library where all modules were compiled into a [`MastForest`].
226///
227/// A library exports a set of one or more procedures. Currently, all exported procedures belong
228/// to the same top-level namespace.
229#[derive(Debug, Clone, PartialEq, Eq)]
230#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
231pub struct Library {
232    /// The content hash of this library, formed by hashing the roots of all exports in
233    /// lexicographical order (by digest, not procedure name)
234    digest: Word,
235    /// A map between procedure paths and the corresponding procedure metadata in the MAST forest.
236    /// Multiple paths can map to the same root, and also, some roots may not be associated with
237    /// any paths.
238    ///
239    /// Note that we use `MastNodeId` as an identifier for procedures instead of MAST root, since 2
240    /// different procedures with the same MAST root can be different due to the decorators they
241    /// contain. However, note that `MastNodeId` is also not a unique identifier for procedures; if
242    /// the procedures have the same MAST root and decorators, they will have the same
243    /// `MastNodeId`.
244    exports: BTreeMap<Arc<Path>, LibraryExport>,
245    /// The MAST forest underlying this library.
246    mast_forest: Arc<MastForest>,
247}
248
249impl AsRef<Library> for Library {
250    #[inline(always)]
251    fn as_ref(&self) -> &Library {
252        self
253    }
254}
255
256// ------------------------------------------------------------------------------------------------
257/// Constructors
258impl Library {
259    /// Constructs a new [`Library`] from the provided MAST forest and a set of exports.
260    ///
261    /// # Errors
262    /// Returns an error if the set of exports is empty.
263    /// Returns an error if any of the specified exports do not have a corresponding procedure root
264    /// in the provided MAST forest.
265    pub fn new(
266        mast_forest: Arc<MastForest>,
267        exports: BTreeMap<Arc<Path>, LibraryExport>,
268    ) -> Result<Self, LibraryError> {
269        if exports.is_empty() {
270            return Err(LibraryError::NoExport);
271        }
272
273        for export in exports.values() {
274            if let LibraryExport::Procedure(ProcedureExport { node, path, .. }) = export
275                && !mast_forest.is_procedure_root(*node)
276            {
277                return Err(LibraryError::NoProcedureRootForExport {
278                    procedure_path: path.clone(),
279                });
280            }
281        }
282
283        let digest =
284            mast_forest.compute_nodes_commitment(exports.values().filter_map(
285                |export| match export {
286                    LibraryExport::Procedure(export) => Some(&export.node),
287                    LibraryExport::Constant(_) | LibraryExport::Type(_) => None,
288                },
289            ));
290
291        Ok(Self { digest, exports, mast_forest })
292    }
293
294    /// Produces a new library with the existing [`MastForest`] and where all key/values in the
295    /// provided advice map are added to the internal advice map.
296    pub fn with_advice_map(mut self, advice_map: AdviceMap) -> Self {
297        self.extend_advice_map(advice_map);
298        self
299    }
300
301    /// Extends the advice map of this library
302    pub fn extend_advice_map(&mut self, advice_map: AdviceMap) {
303        let mast_forest = Arc::make_mut(&mut self.mast_forest);
304        mast_forest.advice_map_mut().extend(advice_map);
305    }
306}
307
308// ------------------------------------------------------------------------------------------------
309/// Public accessors
310impl Library {
311    /// Returns the [Word] representing the content hash of this library
312    pub fn digest(&self) -> &Word {
313        &self.digest
314    }
315
316    /// Returns the fully qualified name and metadata of all procedures exported by the library.
317    pub fn exports(&self) -> impl Iterator<Item = &LibraryExport> {
318        self.exports.values()
319    }
320
321    /// Returns the number of exports in this library.
322    pub fn num_exports(&self) -> usize {
323        self.exports.len()
324    }
325
326    /// Returns a MAST node ID associated with the specified exported procedure.
327    ///
328    /// # Panics
329    /// Panics if the specified procedure is not exported from this library.
330    pub fn get_export_node_id(&self, path: impl AsRef<Path>) -> MastNodeId {
331        let path = path.as_ref().to_absolute();
332        self.exports
333            .get(path.as_ref())
334            .expect("procedure not exported from the library")
335            .unwrap_procedure()
336            .node
337    }
338
339    /// Returns true if the specified exported procedure is re-exported from a dependency.
340    pub fn is_reexport(&self, path: impl AsRef<Path>) -> bool {
341        let path = path.as_ref().to_absolute();
342        self.exports
343            .get(path.as_ref())
344            .and_then(LibraryExport::as_procedure)
345            .map(|export| self.mast_forest[export.node].is_external())
346            .unwrap_or(false)
347    }
348
349    /// Returns a reference to the inner [`MastForest`].
350    pub fn mast_forest(&self) -> &Arc<MastForest> {
351        &self.mast_forest
352    }
353
354    /// Returns the digest of the procedure with the specified name, or `None` if it was not found
355    /// in the library or its library path is malformed.
356    pub fn get_procedure_root_by_path(&self, path: impl AsRef<Path>) -> Option<Word> {
357        let path = path.as_ref().to_absolute();
358        let export = self.exports.get(path.as_ref()).and_then(LibraryExport::as_procedure);
359        export.map(|e| self.mast_forest()[e.node].digest())
360    }
361}
362
363/// Conversions
364impl Library {
365    /// Returns an iterator over the module infos of the library.
366    pub fn module_infos(&self) -> impl Iterator<Item = ModuleInfo> {
367        let mut modules_by_path: BTreeMap<Arc<Path>, ModuleInfo> = BTreeMap::new();
368
369        for export in self.exports.values() {
370            let module_name =
371                Arc::from(export.path().parent().unwrap().to_path_buf().into_boxed_path());
372            let module = modules_by_path
373                .entry(Arc::clone(&module_name))
374                .or_insert_with(|| ModuleInfo::new(module_name, None));
375            match export {
376                LibraryExport::Procedure(ProcedureExport { node, path, signature, attributes }) => {
377                    let proc_digest = self.mast_forest[*node].digest();
378                    let name = path.last().unwrap();
379                    module.add_procedure(
380                        ProcedureName::new(name).expect("valid procedure name"),
381                        proc_digest,
382                        signature.clone().map(Arc::new),
383                        attributes.clone(),
384                    );
385                },
386                LibraryExport::Constant(ConstantExport { path, value }) => {
387                    let name = Ident::new(path.last().unwrap()).expect("valid identifier");
388                    module.add_constant(name, value.clone());
389                },
390                LibraryExport::Type(TypeExport { path, ty }) => {
391                    let name = Ident::new(path.last().unwrap()).expect("valid identifier");
392                    module.add_type(name, ty.clone());
393                },
394            }
395        }
396
397        modules_by_path.into_values()
398    }
399}
400
401#[cfg(feature = "std")]
402impl Library {
403    /// File extension for the Assembly Library.
404    pub const LIBRARY_EXTENSION: &'static str = "masl";
405
406    /// Write the library to a target file
407    ///
408    /// NOTE: It is up to the caller to use the correct file extension, but there is no
409    /// specific requirement that the extension be set, or the same as
410    /// [`Self::LIBRARY_EXTENSION`].
411    pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
412        let path = path.as_ref();
413
414        if let Some(dir) = path.parent() {
415            std::fs::create_dir_all(dir)?;
416        }
417
418        // NOTE: We catch panics due to i/o errors here due to the fact that the ByteWriter
419        // trait does not provide fallible APIs, so WriteAdapter will panic if the underlying
420        // writes fail. This needs to be addressed upstream at some point
421        std::panic::catch_unwind(|| {
422            let mut file = std::fs::File::create(path)?;
423            self.write_into(&mut file);
424            Ok(())
425        })
426        .map_err(|p| {
427            match p.downcast::<std::io::Error>() {
428                // SAFETY: It is guaranteed safe to read Box<std::io::Error>
429                Ok(err) => unsafe { core::ptr::read(&*err) },
430                Err(err) => std::panic::resume_unwind(err),
431            }
432        })?
433    }
434
435    pub fn deserialize_from_file(
436        path: impl AsRef<std::path::Path>,
437    ) -> Result<Self, DeserializationError> {
438        use miden_core::utils::ReadAdapter;
439
440        let path = path.as_ref();
441        let mut file = std::fs::File::open(path).map_err(|err| {
442            DeserializationError::InvalidValue(format!(
443                "failed to open file at {}: {err}",
444                path.to_string_lossy()
445            ))
446        })?;
447        let mut adapter = ReadAdapter::new(&mut file);
448
449        Self::read_from(&mut adapter)
450    }
451}
452
453// KERNEL LIBRARY
454// ================================================================================================
455
456/// Represents a library containing a Miden VM kernel.
457///
458/// This differs from the regular [Library] as follows:
459/// - All exported procedures must be exported directly from the kernel namespace (i.e., `$kernel`).
460/// - There must be at least one exported procedure.
461/// - The number of exported procedures cannot exceed [Kernel::MAX_NUM_PROCEDURES] (i.e., 256).
462#[derive(Debug, Clone, PartialEq, Eq)]
463#[cfg_attr(feature = "serde", derive(Deserialize))]
464#[cfg_attr(feature = "serde", serde(try_from = "Arc<Library>"))]
465pub struct KernelLibrary {
466    #[cfg_attr(feature = "serde", serde(skip))]
467    kernel: Kernel,
468    #[cfg_attr(feature = "serde", serde(skip))]
469    kernel_info: ModuleInfo,
470    library: Arc<Library>,
471}
472
473#[cfg(feature = "serde")]
474impl serde::Serialize for KernelLibrary {
475    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
476    where
477        S: serde::Serializer,
478    {
479        Library::serialize(&self.library, serializer)
480    }
481}
482
483impl AsRef<Library> for KernelLibrary {
484    #[inline(always)]
485    fn as_ref(&self) -> &Library {
486        &self.library
487    }
488}
489
490impl KernelLibrary {
491    /// Returns the [Kernel] for this kernel library.
492    pub fn kernel(&self) -> &Kernel {
493        &self.kernel
494    }
495
496    /// Returns a reference to the inner [`MastForest`].
497    pub fn mast_forest(&self) -> &Arc<MastForest> {
498        self.library.mast_forest()
499    }
500
501    /// Destructures this kernel library into individual parts.
502    pub fn into_parts(self) -> (Kernel, ModuleInfo, Arc<MastForest>) {
503        (self.kernel, self.kernel_info, self.library.mast_forest().clone())
504    }
505}
506
507impl TryFrom<Arc<Library>> for KernelLibrary {
508    type Error = LibraryError;
509
510    fn try_from(library: Arc<Library>) -> Result<Self, Self::Error> {
511        let kernel_path = Arc::from(Path::kernel_path().to_path_buf().into_boxed_path());
512        let mut proc_digests = Vec::with_capacity(library.exports.len());
513
514        let mut kernel_module = ModuleInfo::new(Arc::clone(&kernel_path), None);
515
516        for export in library.exports.values() {
517            match export {
518                LibraryExport::Procedure(export) => {
519                    // make sure all procedures are exported only from the kernel root
520                    if !export.path.is_in_kernel() {
521                        return Err(LibraryError::InvalidKernelExport {
522                            procedure_path: export.path.clone(),
523                        });
524                    }
525
526                    let proc_digest = library.mast_forest[export.node].digest();
527                    proc_digests.push(proc_digest);
528                    kernel_module.add_procedure(
529                        ProcedureName::new(export.path.last().unwrap())
530                            .expect("valid procedure name"),
531                        proc_digest,
532                        export.signature.clone().map(Arc::new),
533                        export.attributes.clone(),
534                    );
535                },
536                LibraryExport::Constant(export) => {
537                    // Only export constants from the kernel root
538                    if export.path.is_in_kernel() {
539                        let name =
540                            Ident::new(export.path.last().unwrap()).expect("valid identifier");
541                        kernel_module.add_constant(name, export.value.clone());
542                    }
543                },
544                LibraryExport::Type(export) => {
545                    // Only export types from the kernel root
546                    if export.path.is_in_kernel() {
547                        let name =
548                            Ident::new(export.path.last().unwrap()).expect("valid identifier");
549                        kernel_module.add_type(name, export.ty.clone());
550                    }
551                },
552            }
553        }
554
555        if proc_digests.is_empty() {
556            return Err(LibraryError::NoExport);
557        }
558
559        let kernel = Kernel::new(&proc_digests).map_err(LibraryError::KernelConversion)?;
560
561        Ok(Self {
562            kernel,
563            kernel_info: kernel_module,
564            library,
565        })
566    }
567}
568
569#[cfg(feature = "std")]
570impl KernelLibrary {
571    /// Write the library to a target file
572    pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
573        self.library.write_to_file(path)
574    }
575}
576
577// LIBRARY SERIALIZATION
578// ================================================================================================
579
580/// NOTE: Serialization of libraries is likely to be deprecated in a future release
581impl Serializable for Library {
582    fn write_into<W: ByteWriter>(&self, target: &mut W) {
583        let Self { digest: _, exports, mast_forest } = self;
584
585        mast_forest.write_into(target);
586
587        target.write_usize(exports.len());
588        for export in exports.values() {
589            export.write_into(target);
590        }
591    }
592}
593
594/// NOTE: Serialization of libraries is likely to be deprecated in a future release
595impl Deserializable for Library {
596    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
597        let mast_forest = Arc::new(MastForest::read_from(source)?);
598
599        let num_exports = source.read_usize()?;
600        if num_exports == 0 {
601            return Err(DeserializationError::InvalidValue(String::from("No exported procedures")));
602        };
603        let mut exports = BTreeMap::new();
604        for _ in 0..num_exports {
605            let tag = source.read_u8()?;
606            let path: PathBuf = source.read()?;
607            let path = Arc::<Path>::from(path.into_boxed_path());
608            let export = match tag {
609                0 => {
610                    let node = MastNodeId::from_u32_safe(source.read_u32()?, &mast_forest)?;
611                    let signature = if source.read_bool()? {
612                        Some(FunctionType::read_from(source)?)
613                    } else {
614                        None
615                    };
616                    let attributes = AttributeSet::read_from(source)?;
617                    LibraryExport::Procedure(ProcedureExport {
618                        node,
619                        path: path.clone(),
620                        signature,
621                        attributes,
622                    })
623                },
624                1 => {
625                    let value = crate::ast::ConstantValue::read_from(source)?;
626                    LibraryExport::Constant(ConstantExport { path: path.clone(), value })
627                },
628                2 => {
629                    let ty = Type::read_from(source)?;
630                    LibraryExport::Type(TypeExport { path: path.clone(), ty })
631                },
632                invalid => {
633                    return Err(DeserializationError::InvalidValue(format!(
634                        "unknown LibraryExport tag: '{invalid}'"
635                    )));
636                },
637            };
638            exports.insert(path, export);
639        }
640
641        let digest =
642            mast_forest.compute_nodes_commitment(exports.values().filter_map(|e| match e {
643                LibraryExport::Procedure(e) => Some(&e.node),
644                LibraryExport::Constant(_) | LibraryExport::Type(_) => None,
645            }));
646
647        Ok(Self { digest, exports, mast_forest })
648    }
649}
650
651#[cfg(feature = "serde")]
652impl serde::Serialize for Library {
653    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
654    where
655        S: serde::Serializer,
656    {
657        use serde::ser::SerializeStruct;
658
659        struct LibraryExports<'a>(&'a BTreeMap<Arc<Path>, LibraryExport>);
660        impl serde::Serialize for LibraryExports<'_> {
661            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
662            where
663                S: serde::Serializer,
664            {
665                use serde::ser::SerializeSeq;
666                let mut serializer = serializer.serialize_seq(Some(self.0.len()))?;
667                for elem in self.0.values() {
668                    serializer.serialize_element(elem)?;
669                }
670                serializer.end()
671            }
672        }
673
674        let Self { digest: _, exports, mast_forest } = self;
675
676        let mut serializer = serializer.serialize_struct("Library", 2)?;
677        serializer.serialize_field("mast_forest", mast_forest)?;
678        serializer.serialize_field("exports", &LibraryExports(exports))?;
679        serializer.end()
680    }
681}
682
683#[cfg(feature = "serde")]
684impl<'de> serde::Deserialize<'de> for Library {
685    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
686    where
687        D: serde::Deserializer<'de>,
688    {
689        use serde::de::Visitor;
690
691        #[derive(Deserialize)]
692        #[serde(field_identifier, rename_all = "snake_case")]
693        enum Field {
694            MastForest,
695            Exports,
696        }
697
698        struct LibraryVisitor;
699
700        impl<'de> Visitor<'de> for LibraryVisitor {
701            type Value = Library;
702
703            fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
704                formatter.write_str("struct Library")
705            }
706
707            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
708            where
709                A: serde::de::SeqAccess<'de>,
710            {
711                let mast_forest = seq
712                    .next_element()?
713                    .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
714                let exports: Vec<LibraryExport> = seq
715                    .next_element()?
716                    .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
717                let exports = exports.into_iter().map(|export| (export.path(), export)).collect();
718                Library::new(mast_forest, exports).map_err(serde::de::Error::custom)
719            }
720
721            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
722            where
723                A: serde::de::MapAccess<'de>,
724            {
725                let mut mast_forest = None;
726                let mut exports = None;
727                while let Some(key) = map.next_key()? {
728                    match key {
729                        Field::MastForest => {
730                            if mast_forest.is_some() {
731                                return Err(serde::de::Error::duplicate_field("mast_forest"));
732                            }
733                            mast_forest = Some(map.next_value()?);
734                        },
735                        Field::Exports => {
736                            if exports.is_some() {
737                                return Err(serde::de::Error::duplicate_field("exports"));
738                            }
739                            let items: Vec<LibraryExport> = map.next_value()?;
740                            exports = Some(
741                                items.into_iter().map(|export| (export.path(), export)).collect(),
742                            );
743                        },
744                    }
745                }
746                let mast_forest =
747                    mast_forest.ok_or_else(|| serde::de::Error::missing_field("mast_forest"))?;
748                let exports = exports.ok_or_else(|| serde::de::Error::missing_field("exports"))?;
749                Library::new(mast_forest, exports).map_err(serde::de::Error::custom)
750            }
751        }
752
753        deserializer.deserialize_struct("Library", &["mast_forest", "exports"], LibraryVisitor)
754    }
755}
756
757/// NOTE: Serialization of libraries is likely to be deprecated in a future release
758impl Serializable for KernelLibrary {
759    fn write_into<W: ByteWriter>(&self, target: &mut W) {
760        let Self { kernel: _, kernel_info: _, library } = self;
761
762        library.write_into(target);
763    }
764}
765
766/// NOTE: Serialization of libraries is likely to be deprecated in a future release
767impl Deserializable for KernelLibrary {
768    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
769        let library = Arc::new(Library::read_from(source)?);
770
771        Self::try_from(library).map_err(|err| {
772            DeserializationError::InvalidValue(format!(
773                "Failed to deserialize kernel library: {err}"
774            ))
775        })
776    }
777}
778
779/// NOTE: Deserialization is handled in the implementation for [Library]
780impl Serializable for LibraryExport {
781    fn write_into<W: ByteWriter>(&self, target: &mut W) {
782        match self {
783            LibraryExport::Procedure(ProcedureExport {
784                node,
785                path: name,
786                signature,
787                attributes,
788            }) => {
789                target.write_u8(0);
790                name.write_into(target);
791                target.write_u32(u32::from(*node));
792                if let Some(sig) = signature {
793                    target.write_bool(true);
794                    sig.write_into(target);
795                } else {
796                    target.write_bool(false);
797                }
798                attributes.write_into(target);
799            },
800            LibraryExport::Constant(ConstantExport { path: name, value }) => {
801                target.write_u8(1);
802                name.write_into(target);
803                value.write_into(target);
804            },
805            LibraryExport::Type(TypeExport { path: name, ty }) => {
806                target.write_u8(2);
807                name.write_into(target);
808                ty.write_into(target);
809            },
810        }
811    }
812}
813
814#[cfg(feature = "arbitrary")]
815impl proptest::prelude::Arbitrary for Library {
816    type Parameters = ();
817
818    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
819        use miden_core::{
820            mast::{BasicBlockNodeBuilder, MastForestContributor},
821            operations::Operation,
822        };
823        use proptest::prelude::*;
824
825        prop::collection::vec(any::<LibraryExport>(), 1..5)
826            .prop_map(|exports| {
827                let mut exports =
828                    BTreeMap::from_iter(exports.into_iter().map(|export| (export.path(), export)));
829                // Create a MastForest with actual nodes for the exports
830                let mut mast_forest = MastForest::new();
831                let mut nodes = Vec::new();
832
833                for export in exports.values() {
834                    if let LibraryExport::Procedure(export) = export {
835                        let node_id = BasicBlockNodeBuilder::new(
836                            vec![Operation::Add, Operation::Mul],
837                            Vec::new(),
838                        )
839                        .add_to_forest(&mut mast_forest)
840                        .unwrap();
841                        nodes.push((export.node, node_id));
842                    }
843                }
844
845                // Replace the export node IDs with the actual node IDs we created
846                let mut procedure_exports = 0;
847                for export in exports.values_mut() {
848                    match export {
849                        LibraryExport::Procedure(export) => {
850                            procedure_exports += 1;
851                            // Find the corresponding node we created
852                            if let Some(&(_, actual_node_id)) =
853                                nodes.iter().find(|(original_id, _)| *original_id == export.node)
854                            {
855                                export.node = actual_node_id;
856                            } else {
857                                // If we can't find the node (shouldn't happen), use the first node
858                                // we created
859                                if let Some(&(_, first_node_id)) = nodes.first() {
860                                    export.node = first_node_id;
861                                } else {
862                                    // This should never happen since we create nodes for each
863                                    // export
864                                    panic!("No nodes created for exports");
865                                }
866                            }
867                        },
868                        LibraryExport::Constant(_) | LibraryExport::Type(_) => (),
869                    }
870                }
871
872                let mut node_ids = Vec::with_capacity(procedure_exports);
873                for export in exports.values() {
874                    if let LibraryExport::Procedure(export) = export {
875                        // Add the node to the forest roots if it's not already there
876                        mast_forest.make_root(export.node);
877                        // Collect the node id for recomputing the digest
878                        node_ids.push(export.node);
879                    }
880                }
881
882                // Recompute the digest
883                let digest = mast_forest.compute_nodes_commitment(&node_ids);
884
885                let mast_forest = Arc::new(mast_forest);
886                Library { digest, exports, mast_forest }
887            })
888            .boxed()
889    }
890
891    type Strategy = proptest::prelude::BoxedStrategy<Self>;
892}