1use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec};
2
3use miden_core::{
4 Word,
5 advice::AdviceMap,
6 mast::{MastForest, MastNodeExt, MastNodeId, UntrustedMastForest},
7 program::Kernel,
8 serde::{
9 ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader,
10 },
11};
12use midenc_hir_type::{FunctionType, Type};
13#[cfg(feature = "arbitrary")]
14use proptest::prelude::*;
15#[cfg(feature = "serde")]
16use serde::{Deserialize, Serialize};
17
18#[cfg(feature = "arbitrary")]
19use crate::ast::QualifiedProcedureName;
20#[cfg(feature = "serde")]
21use crate::ast::path;
22use crate::ast::{AttributeSet, ConstantValue, Ident, Path, PathBuf, PathComponent, ProcedureName};
23
24mod error;
25mod module;
26
27pub use module::{ConstantInfo, ItemInfo, ModuleInfo, ProcedureInfo, TypeInfo};
28pub use semver::{Error as VersionError, Version};
29
30pub use self::error::LibraryError;
31
32#[derive(Debug, Clone, PartialEq, Eq)]
37#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
39pub enum LibraryExport {
40 Procedure(ProcedureExport),
41 Constant(ConstantExport),
42 Type(TypeExport),
43}
44
45impl LibraryExport {
46 pub fn path(&self) -> Arc<Path> {
47 match self {
48 Self::Procedure(export) => export.path.clone(),
49 Self::Constant(export) => export.path.clone(),
50 Self::Type(export) => export.path.clone(),
51 }
52 }
53
54 pub fn as_procedure(&self) -> Option<&ProcedureExport> {
55 match self {
56 Self::Procedure(proc) => Some(proc),
57 Self::Constant(_) | Self::Type(_) => None,
58 }
59 }
60
61 pub fn unwrap_procedure(&self) -> &ProcedureExport {
62 match self {
63 Self::Procedure(proc) => proc,
64 Self::Constant(_) | Self::Type(_) => panic!("expected export to be a procedure"),
65 }
66 }
67}
68
69impl From<ProcedureExport> for LibraryExport {
70 fn from(value: ProcedureExport) -> Self {
71 Self::Procedure(value)
72 }
73}
74
75impl From<ConstantExport> for LibraryExport {
76 fn from(value: ConstantExport) -> Self {
77 Self::Constant(value)
78 }
79}
80
81impl From<TypeExport> for LibraryExport {
82 fn from(value: TypeExport) -> Self {
83 Self::Type(value)
84 }
85}
86
87#[cfg(feature = "arbitrary")]
88impl Arbitrary for LibraryExport {
89 type Parameters = ();
90
91 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
92 use proptest::{arbitrary::any, prop_oneof, strategy::Strategy};
93
94 prop_oneof![
95 any::<ProcedureExport>().prop_map(Self::Procedure),
96 any::<ConstantExport>().prop_map(Self::Constant),
97 any::<TypeExport>().prop_map(Self::Type),
98 ]
99 .boxed()
100 }
101
102 type Strategy = BoxedStrategy<Self>;
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
106#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
107#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
108pub struct ProcedureExport {
109 pub node: MastNodeId,
111 #[cfg_attr(feature = "serde", serde(with = "path"))]
113 pub path: Arc<Path>,
114 #[cfg_attr(feature = "serde", serde(default))]
116 pub signature: Option<FunctionType>,
117 #[cfg_attr(feature = "serde", serde(default))]
118 pub attributes: AttributeSet,
119}
120
121impl ProcedureExport {
122 pub fn new(node: MastNodeId, path: Arc<Path>) -> Self {
124 Self {
125 node,
126 path,
127 signature: None,
128 attributes: Default::default(),
129 }
130 }
131
132 pub fn with_signature(mut self, signature: FunctionType) -> Self {
134 self.signature = Some(signature);
135 self
136 }
137
138 pub fn with_attributes(mut self, attrs: AttributeSet) -> Self {
140 self.attributes = attrs;
141 self
142 }
143}
144
145#[cfg(feature = "arbitrary")]
146impl Arbitrary for ProcedureExport {
147 type Parameters = ();
148
149 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
150 use proptest::collection::vec as prop_vec;
151 use smallvec::SmallVec;
152
153 let simple_type = prop_oneof![Just(Type::Felt), Just(Type::U32), Just(Type::U64),];
155
156 let params = prop_vec(simple_type.clone(), 0..=4);
158 let results = prop_vec(simple_type, 0..=2);
159
160 let abi = Just(midenc_hir_type::CallConv::Fast);
162
163 let signature =
165 prop::option::of((abi, params, results).prop_map(|(abi, params_vec, results_vec)| {
166 let params = SmallVec::<[Type; 4]>::from_vec(params_vec);
167 let results = SmallVec::<[Type; 1]>::from_vec(results_vec);
168 FunctionType { abi, params, results }
169 }));
170
171 let nid = any::<MastNodeId>();
172 let name = any::<QualifiedProcedureName>();
173 (nid, name, signature)
174 .prop_map(|(nodeid, procname, signature)| Self {
175 node: nodeid,
176 path: procname.to_path_buf().into(),
177 signature,
178 attributes: Default::default(),
179 })
180 .boxed()
181 }
182
183 type Strategy = BoxedStrategy<Self>;
184}
185
186#[derive(Debug, Clone, PartialEq, Eq)]
187#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
188#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
189pub struct ConstantExport {
190 #[cfg_attr(feature = "serde", serde(with = "path"))]
192 pub path: Arc<Path>,
193 pub value: ConstantValue,
195}
196
197#[cfg(feature = "arbitrary")]
198impl Arbitrary for ConstantExport {
199 type Parameters = ();
200
201 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
202 let path = crate::arbitrary::path::constant_path_random_length(1);
203 let value = any::<ConstantValue>();
204
205 (path, value).prop_map(|(path, value)| Self { path, value }).boxed()
206 }
207
208 type Strategy = BoxedStrategy<Self>;
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
212#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
213#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
214pub struct TypeExport {
215 #[cfg_attr(feature = "serde", serde(with = "path"))]
217 pub path: Arc<Path>,
218 pub ty: Type,
220}
221
222#[cfg(feature = "arbitrary")]
223impl Arbitrary for TypeExport {
224 type Parameters = ();
225
226 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
227 use proptest::strategy::{Just, Strategy};
228 let path = crate::arbitrary::path::user_defined_type_path_random_length(1);
229 let ty = Just(Type::Felt);
230
231 (path, ty).prop_map(|(path, ty)| Self { path, ty }).boxed()
232 }
233
234 type Strategy = BoxedStrategy<Self>;
235}
236
237#[derive(Debug, Clone, PartialEq, Eq)]
245#[cfg_attr(
246 all(feature = "arbitrary", test),
247 miden_test_serde_macros::serde_test(binary_serde(true))
248)]
249pub struct Library {
250 digest: Word,
253 exports: BTreeMap<Arc<Path>, LibraryExport>,
263 mast_forest: Arc<MastForest>,
265}
266
267impl AsRef<Library> for Library {
268 #[inline(always)]
269 fn as_ref(&self) -> &Library {
270 self
271 }
272}
273
274impl Library {
277 pub fn new(
284 mast_forest: Arc<MastForest>,
285 exports: BTreeMap<Arc<Path>, LibraryExport>,
286 ) -> Result<Self, LibraryError> {
287 if exports.is_empty() {
288 return Err(LibraryError::NoExport);
289 }
290
291 for export in exports.values() {
292 if let LibraryExport::Procedure(ProcedureExport { node, path, .. }) = export
293 && !mast_forest.is_procedure_root(*node)
294 {
295 return Err(LibraryError::NoProcedureRootForExport {
296 procedure_path: path.clone(),
297 });
298 }
299 }
300
301 let digest =
302 mast_forest.compute_nodes_commitment(exports.values().filter_map(
303 |export| match export {
304 LibraryExport::Procedure(export) => Some(&export.node),
305 LibraryExport::Constant(_) | LibraryExport::Type(_) => None,
306 },
307 ));
308
309 Ok(Self { digest, exports, mast_forest })
310 }
311
312 pub fn with_advice_map(mut self, advice_map: AdviceMap) -> Self {
315 self.extend_advice_map(advice_map);
316 self
317 }
318
319 pub fn extend_advice_map(&mut self, advice_map: AdviceMap) {
321 let mast_forest = Arc::make_mut(&mut self.mast_forest);
322 mast_forest.advice_map_mut().extend(advice_map);
323 }
324
325 fn read_mast_forest<R: ByteReader>(
326 source: &mut R,
327 validate_mast_forest: bool,
328 ) -> Result<Arc<MastForest>, DeserializationError> {
329 let mast_forest = if validate_mast_forest {
330 UntrustedMastForest::read_from(source)?.validate().map_err(|err| {
331 DeserializationError::InvalidValue(format!(
332 "library contains an invalid untrusted MAST forest: {err}"
333 ))
334 })?
335 } else {
336 MastForest::read_from(source)?
337 };
338
339 Ok(Arc::new(mast_forest))
340 }
341
342 fn read_from_with_mast_forest<R: ByteReader>(
343 source: &mut R,
344 mast_forest: Arc<MastForest>,
345 ) -> Result<Self, DeserializationError> {
346 let num_exports = source.read_usize()?;
347 if num_exports == 0 {
348 return Err(DeserializationError::InvalidValue(String::from("No exported procedures")));
349 };
350 let mut exports = BTreeMap::new();
351 for _ in 0..num_exports {
352 let tag = source.read_u8()?;
353 let path: PathBuf = source.read()?;
354 let path = Arc::<Path>::from(path.into_boxed_path());
355 let export = match tag {
356 0 => {
357 let node = MastNodeId::from_u32_safe(source.read_u32()?, &mast_forest)?;
358 let signature = if source.read_bool()? {
359 Some(FunctionType::read_from(source)?)
360 } else {
361 None
362 };
363 let attributes = AttributeSet::read_from(source)?;
364 LibraryExport::Procedure(ProcedureExport {
365 node,
366 path: path.clone(),
367 signature,
368 attributes,
369 })
370 },
371 1 => {
372 let value = ConstantValue::read_from(source)?;
373 LibraryExport::Constant(ConstantExport { path: path.clone(), value })
374 },
375 2 => {
376 let ty = Type::read_from(source)?;
377 LibraryExport::Type(TypeExport { path: path.clone(), ty })
378 },
379 invalid => {
380 return Err(DeserializationError::InvalidValue(format!(
381 "unknown LibraryExport tag: '{invalid}'"
382 )));
383 },
384 };
385 let (path, export) = normalize_export_for_deserialization(export)
386 .map_err(DeserializationError::InvalidValue)?;
387 if exports.insert(path.clone(), export).is_some() {
388 return Err(DeserializationError::InvalidValue(format!(
389 "duplicate canonical export path in library artifact: '{path}'"
390 )));
391 }
392 }
393
394 Self::new(mast_forest, exports)
395 .map_err(|err| DeserializationError::InvalidValue(format!("{err}")))
396 }
397
398 pub fn read_from_unchecked<R: ByteReader>(
408 source: &mut R,
409 ) -> Result<Self, DeserializationError> {
410 let mast_forest = Self::read_mast_forest(source, false)?;
411 Self::read_from_with_mast_forest(source, mast_forest)
412 }
413
414 pub fn read_from_bytes_unchecked(bytes: &[u8]) -> Result<Self, DeserializationError> {
418 let mut source = SliceReader::new(bytes);
419 Self::read_from_unchecked(&mut source)
420 }
421}
422
423impl Library {
426 pub fn digest(&self) -> &Word {
428 &self.digest
429 }
430
431 pub fn exports(&self) -> impl Iterator<Item = &LibraryExport> {
433 self.exports.values()
434 }
435
436 pub fn num_exports(&self) -> usize {
438 self.exports.len()
439 }
440
441 pub fn get_export_node_id(&self, path: impl AsRef<Path>) -> MastNodeId {
446 let path = path.as_ref().to_absolute();
447 self.exports
448 .get(path.as_ref())
449 .expect("procedure not exported from the library")
450 .unwrap_procedure()
451 .node
452 }
453
454 pub fn is_reexport(&self, path: impl AsRef<Path>) -> bool {
456 let path = path.as_ref().to_absolute();
457 self.exports
458 .get(path.as_ref())
459 .and_then(LibraryExport::as_procedure)
460 .map(|export| self.mast_forest[export.node].is_external())
461 .unwrap_or(false)
462 }
463
464 pub fn mast_forest(&self) -> &Arc<MastForest> {
466 &self.mast_forest
467 }
468
469 pub fn get_procedure_root_by_path(&self, path: impl AsRef<Path>) -> Option<Word> {
472 let path = path.as_ref().to_absolute();
473 let export = self.exports.get(path.as_ref()).and_then(LibraryExport::as_procedure);
474 export.map(|e| self.mast_forest()[e.node].digest())
475 }
476
477 pub fn get_procedure_node_by_path(&self, path: impl AsRef<Path>) -> Option<MastNodeId> {
479 let path = path.as_ref().to_absolute();
480 self.exports
481 .get(path.as_ref())
482 .and_then(LibraryExport::as_procedure)
483 .map(|export| export.node)
484 }
485}
486
487impl Library {
489 pub fn module_infos(&self) -> impl Iterator<Item = ModuleInfo> {
491 let mut modules_by_path: BTreeMap<Arc<Path>, ModuleInfo> = BTreeMap::new();
492
493 for export in self.exports.values() {
494 let module_name =
495 Arc::from(export.path().parent().unwrap().to_path_buf().into_boxed_path());
496 let module = modules_by_path
497 .entry(Arc::clone(&module_name))
498 .or_insert_with(|| ModuleInfo::new(module_name, None));
499 match export {
500 LibraryExport::Procedure(ProcedureExport { node, path, signature, attributes }) => {
501 let proc_digest = self.mast_forest[*node].digest();
502 let name = path.last().unwrap();
503 module.add_procedure_with_provenance(
504 ProcedureName::new(name).expect("valid procedure name"),
505 proc_digest,
506 signature.clone().map(Arc::new),
507 attributes.clone(),
508 Some(*node),
509 Some(self.mast_forest.commitment()),
510 );
511 },
512 LibraryExport::Constant(ConstantExport { path, value }) => {
513 let name = Ident::new(path.last().unwrap()).expect("valid identifier");
514 module.add_constant(name, value.clone());
515 },
516 LibraryExport::Type(TypeExport { path, ty }) => {
517 let name = Ident::new(path.last().unwrap()).expect("valid identifier");
518 module.add_type(name, ty.clone());
519 },
520 }
521 }
522
523 modules_by_path.into_values()
524 }
525}
526
527#[cfg(feature = "std")]
528impl Library {
529 pub const LIBRARY_EXTENSION: &'static str = "masl";
531
532 pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
538 let path = path.as_ref();
539
540 if let Some(dir) = path.parent() {
541 std::fs::create_dir_all(dir)?;
542 }
543
544 std::panic::catch_unwind(|| {
548 let mut file = std::fs::File::create(path)?;
549 self.write_into(&mut file);
550 Ok(())
551 })
552 .map_err(|p| match p.downcast::<std::io::Error>() {
553 Ok(err) => *err,
554 Err(err) => std::panic::resume_unwind(err),
555 })?
556 }
557
558 pub fn deserialize_from_file(
559 path: impl AsRef<std::path::Path>,
560 ) -> Result<Self, DeserializationError> {
561 use miden_core::utils::ReadAdapter;
562
563 let path = path.as_ref();
564 let mut file = std::fs::File::open(path).map_err(|err| {
565 DeserializationError::InvalidValue(format!(
566 "failed to open file at {}: {err}",
567 path.to_string_lossy()
568 ))
569 })?;
570 let mut adapter = ReadAdapter::new(&mut file);
571
572 Self::read_from(&mut adapter)
573 }
574}
575
576#[derive(Debug, Clone, PartialEq, Eq)]
586#[cfg_attr(feature = "serde", derive(Deserialize))]
587#[cfg_attr(feature = "serde", serde(try_from = "Arc<Library>"))]
588pub struct KernelLibrary {
589 #[cfg_attr(feature = "serde", serde(skip))]
590 kernel: Kernel,
591 #[cfg_attr(feature = "serde", serde(skip))]
592 kernel_info: ModuleInfo,
593 library: Arc<Library>,
594}
595
596#[cfg(feature = "serde")]
597impl Serialize for KernelLibrary {
598 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
599 where
600 S: serde::Serializer,
601 {
602 Library::serialize(&self.library, serializer)
603 }
604}
605
606impl AsRef<Library> for KernelLibrary {
607 #[inline(always)]
608 fn as_ref(&self) -> &Library {
609 &self.library
610 }
611}
612
613impl KernelLibrary {
614 fn try_from_library(library: Library) -> Result<Self, DeserializationError> {
615 Self::try_from(Arc::new(library)).map_err(|err| {
616 DeserializationError::InvalidValue(format!(
617 "Failed to deserialize kernel library: {err}"
618 ))
619 })
620 }
621
622 pub fn kernel(&self) -> &Kernel {
624 &self.kernel
625 }
626
627 pub fn mast_forest(&self) -> &Arc<MastForest> {
629 self.library.mast_forest()
630 }
631
632 pub fn into_parts(self) -> (Kernel, ModuleInfo, Arc<MastForest>) {
634 (self.kernel, self.kernel_info, self.library.mast_forest().clone())
635 }
636
637 pub fn read_from_unchecked<R: ByteReader>(
647 source: &mut R,
648 ) -> Result<Self, DeserializationError> {
649 let library = Library::read_from_unchecked(source)?;
650 Self::try_from_library(library)
651 }
652
653 pub fn read_from_bytes_unchecked(bytes: &[u8]) -> Result<Self, DeserializationError> {
657 let mut source = SliceReader::new(bytes);
658 Self::read_from_unchecked(&mut source)
659 }
660}
661
662impl TryFrom<Arc<Library>> for KernelLibrary {
663 type Error = LibraryError;
664
665 fn try_from(library: Arc<Library>) -> Result<Self, Self::Error> {
666 let kernel_path = Arc::from(Path::kernel_path().to_path_buf().into_boxed_path());
667 let mut proc_digests = Vec::with_capacity(library.exports.len());
668
669 let mut kernel_module = ModuleInfo::new(Arc::clone(&kernel_path), None);
670
671 for export in library.exports.values() {
672 match export {
673 LibraryExport::Procedure(export) => {
674 if !export.path.is_in_kernel() {
676 return Err(LibraryError::InvalidKernelExport {
677 procedure_path: export.path.clone(),
678 });
679 }
680
681 let proc_digest = library.mast_forest[export.node].digest();
682 proc_digests.push(proc_digest);
683 kernel_module.add_procedure_with_provenance(
684 ProcedureName::new(export.path.last().unwrap())
685 .expect("valid procedure name"),
686 proc_digest,
687 export.signature.clone().map(Arc::new),
688 export.attributes.clone(),
689 Some(export.node),
690 Some(library.mast_forest.commitment()),
691 );
692 },
693 LibraryExport::Constant(export) => {
694 if export.path.is_in_kernel() {
696 let name =
697 Ident::new(export.path.last().unwrap()).expect("valid identifier");
698 kernel_module.add_constant(name, export.value.clone());
699 }
700 },
701 LibraryExport::Type(export) => {
702 if export.path.is_in_kernel() {
704 let name =
705 Ident::new(export.path.last().unwrap()).expect("valid identifier");
706 kernel_module.add_type(name, export.ty.clone());
707 }
708 },
709 }
710 }
711
712 if proc_digests.is_empty() {
713 return Err(LibraryError::NoExport);
714 }
715
716 let kernel = Kernel::new(&proc_digests).map_err(LibraryError::KernelConversion)?;
717
718 Ok(Self {
719 kernel,
720 kernel_info: kernel_module,
721 library,
722 })
723 }
724}
725
726#[cfg(feature = "std")]
727impl KernelLibrary {
728 pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
730 self.library.write_to_file(path)
731 }
732}
733
734fn export_raw_leaf(path: &Path) -> Result<&str, String> {
738 match path.components().next_back() {
739 Some(Ok(PathComponent::Normal(leaf))) => Ok(leaf),
740 Some(Err(err)) => Err(format!("invalid export path '{path}': {err}")),
741 Some(Ok(PathComponent::Root)) | None => {
742 Err(format!("invalid export path (missing export leaf): '{path}'"))
743 },
744 }
745}
746
747fn canonicalize_export_path(path: &Path) -> Result<Arc<Path>, String> {
748 let canonical = path
749 .to_path_buf()
750 .canonicalize()
751 .map_err(|err| format!("invalid export path '{path}': {err}"))?;
752 Ok(Arc::<Path>::from(canonical.into_boxed_path()))
753}
754
755fn normalize_export_for_deserialization(
756 mut export: LibraryExport,
757) -> Result<(Arc<Path>, LibraryExport), String> {
758 let canonical_path = canonicalize_export_path(export.path().as_ref())?;
759 let leaf = export_raw_leaf(canonical_path.as_ref())?;
760
761 match &export {
762 LibraryExport::Procedure(_) => {
763 ProcedureName::new(leaf).map_err(|err| {
764 format!(
765 "invalid procedure export leaf name '{leaf}' in path '{canonical_path}': {err}"
766 )
767 })?;
768 },
769 LibraryExport::Constant(_) | LibraryExport::Type(_) => {
770 Ident::new(leaf).map_err(|err| {
771 format!("invalid export leaf name '{leaf}' in path '{canonical_path}': {err}")
772 })?;
773 },
774 }
775
776 match &mut export {
777 LibraryExport::Procedure(export) => export.path = canonical_path.clone(),
778 LibraryExport::Constant(export) => export.path = canonical_path.clone(),
779 LibraryExport::Type(export) => export.path = canonical_path.clone(),
780 }
781
782 Ok((canonical_path, export))
783}
784
785impl Serializable for Library {
787 fn write_into<W: ByteWriter>(&self, target: &mut W) {
788 let Self { digest: _, exports, mast_forest } = self;
789
790 mast_forest.write_into(target);
791
792 target.write_usize(exports.len());
793 for export in exports.values() {
794 export.write_into(target);
795 }
796 }
797}
798
799impl Deserializable for Library {
801 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
802 let mast_forest = Self::read_mast_forest(source, true)?;
803 Self::read_from_with_mast_forest(source, mast_forest)
804 }
805}
806
807#[cfg(feature = "serde")]
808impl Serialize for Library {
809 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
810 where
811 S: serde::Serializer,
812 {
813 use serde::ser::SerializeStruct;
814
815 struct LibraryExports<'a>(&'a BTreeMap<Arc<Path>, LibraryExport>);
816 impl Serialize for LibraryExports<'_> {
817 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
818 where
819 S: serde::Serializer,
820 {
821 use serde::ser::SerializeSeq;
822 let mut serializer = serializer.serialize_seq(Some(self.0.len()))?;
823 for elem in self.0.values() {
824 serializer.serialize_element(elem)?;
825 }
826 serializer.end()
827 }
828 }
829
830 let Self { digest: _, exports, mast_forest } = self;
831
832 let mut serializer = serializer.serialize_struct("Library", 2)?;
833 serializer.serialize_field("mast_forest", mast_forest)?;
834 serializer.serialize_field("exports", &LibraryExports(exports))?;
835 serializer.end()
836 }
837}
838
839#[cfg(feature = "serde")]
840impl<'de> Deserialize<'de> for Library {
841 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
842 where
843 D: serde::Deserializer<'de>,
844 {
845 use serde::de::Visitor;
846
847 #[derive(Deserialize)]
848 #[serde(field_identifier, rename_all = "snake_case")]
849 enum Field {
850 MastForest,
851 Exports,
852 }
853
854 struct LibraryVisitor;
855
856 impl LibraryVisitor {
857 fn normalize_exports<E>(
858 items: Vec<LibraryExport>,
859 ) -> Result<BTreeMap<Arc<Path>, LibraryExport>, E>
860 where
861 E: serde::de::Error,
862 {
863 let mut exports = BTreeMap::new();
864 for export in items {
865 let (path, export) = normalize_export_for_deserialization(export)
866 .map_err(serde::de::Error::custom)?;
867 if exports.insert(path.clone(), export).is_some() {
868 return Err(serde::de::Error::custom(format!(
869 "duplicate canonical export path in library artifact: '{path}'"
870 )));
871 }
872 }
873
874 Ok(exports)
875 }
876 }
877
878 impl<'de> Visitor<'de> for LibraryVisitor {
879 type Value = Library;
880
881 fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
882 formatter.write_str("struct Library")
883 }
884
885 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
886 where
887 A: serde::de::SeqAccess<'de>,
888 {
889 let mast_forest = seq
890 .next_element()?
891 .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
892 let export_items: Vec<LibraryExport> = seq
893 .next_element()?
894 .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
895 let exports = Self::normalize_exports(export_items)?;
896 Library::new(mast_forest, exports).map_err(serde::de::Error::custom)
897 }
898
899 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
900 where
901 A: serde::de::MapAccess<'de>,
902 {
903 let mut mast_forest = None;
904 let mut exports = None;
905 while let Some(key) = map.next_key()? {
906 match key {
907 Field::MastForest => {
908 if mast_forest.is_some() {
909 return Err(serde::de::Error::duplicate_field("mast_forest"));
910 }
911 mast_forest = Some(map.next_value()?);
912 },
913 Field::Exports => {
914 if exports.is_some() {
915 return Err(serde::de::Error::duplicate_field("exports"));
916 }
917 let items: Vec<LibraryExport> = map.next_value()?;
918 exports = Some(Self::normalize_exports(items)?);
919 },
920 }
921 }
922 let mast_forest =
923 mast_forest.ok_or_else(|| serde::de::Error::missing_field("mast_forest"))?;
924 let exports = exports.ok_or_else(|| serde::de::Error::missing_field("exports"))?;
925 Library::new(mast_forest, exports).map_err(serde::de::Error::custom)
926 }
927 }
928
929 deserializer.deserialize_struct("Library", &["mast_forest", "exports"], LibraryVisitor)
930 }
931}
932
933impl Serializable for KernelLibrary {
935 fn write_into<W: ByteWriter>(&self, target: &mut W) {
936 let Self { kernel: _, kernel_info: _, library } = self;
937
938 library.write_into(target);
939 }
940}
941
942impl Deserializable for KernelLibrary {
944 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
945 let library = Library::read_from(source)?;
946 Self::try_from_library(library)
947 }
948}
949
950impl Serializable for LibraryExport {
952 fn write_into<W: ByteWriter>(&self, target: &mut W) {
953 match self {
954 LibraryExport::Procedure(ProcedureExport {
955 node,
956 path: name,
957 signature,
958 attributes,
959 }) => {
960 target.write_u8(0);
961 name.write_into(target);
962 target.write_u32(u32::from(*node));
963 if let Some(sig) = signature {
964 target.write_bool(true);
965 sig.write_into(target);
966 } else {
967 target.write_bool(false);
968 }
969 attributes.write_into(target);
970 },
971 LibraryExport::Constant(ConstantExport { path: name, value }) => {
972 target.write_u8(1);
973 name.write_into(target);
974 value.write_into(target);
975 },
976 LibraryExport::Type(TypeExport { path: name, ty }) => {
977 target.write_u8(2);
978 name.write_into(target);
979 ty.write_into(target);
980 },
981 }
982 }
983}
984
985#[cfg(feature = "arbitrary")]
986impl Arbitrary for Library {
987 type Parameters = ();
988
989 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
990 use miden_core::{
991 mast::{BasicBlockNodeBuilder, MastForestContributor},
992 operations::Operation,
993 };
994 use proptest::prelude::*;
995
996 prop::collection::vec(any::<LibraryExport>(), 1..5)
997 .prop_map(|exports| {
998 let mut exports =
999 BTreeMap::from_iter(exports.into_iter().map(|export| (export.path(), export)));
1000 let mut mast_forest = MastForest::new();
1002 let mut nodes = Vec::new();
1003
1004 for export in exports.values() {
1005 if let LibraryExport::Procedure(export) = export {
1006 let node_id = BasicBlockNodeBuilder::new(
1007 vec![Operation::Add, Operation::Mul],
1008 Vec::new(),
1009 )
1010 .add_to_forest(&mut mast_forest)
1011 .unwrap();
1012 nodes.push((export.node, node_id));
1013 }
1014 }
1015
1016 let mut procedure_exports = 0;
1018 for export in exports.values_mut() {
1019 match export {
1020 LibraryExport::Procedure(export) => {
1021 procedure_exports += 1;
1022 if let Some(&(_, actual_node_id)) =
1024 nodes.iter().find(|(original_id, _)| *original_id == export.node)
1025 {
1026 export.node = actual_node_id;
1027 } else {
1028 if let Some(&(_, first_node_id)) = nodes.first() {
1031 export.node = first_node_id;
1032 } else {
1033 panic!("No nodes created for exports");
1036 }
1037 }
1038 },
1039 LibraryExport::Constant(_) | LibraryExport::Type(_) => (),
1040 }
1041 }
1042
1043 let mut node_ids = Vec::with_capacity(procedure_exports);
1044 for export in exports.values() {
1045 if let LibraryExport::Procedure(export) = export {
1046 mast_forest.make_root(export.node);
1048 node_ids.push(export.node);
1050 }
1051 }
1052
1053 let digest = mast_forest.compute_nodes_commitment(&node_ids);
1055
1056 let mast_forest = Arc::new(mast_forest);
1057 Library { digest, exports, mast_forest }
1058 })
1059 .boxed()
1060 }
1061
1062 type Strategy = BoxedStrategy<Self>;
1063}
1064
1065#[cfg(test)]
1066mod tests {
1067 #[cfg(feature = "serde")]
1068 use super::*;
1069
1070 #[cfg(feature = "serde")]
1071 #[test]
1072 fn serde_library_deserialization_rejects_duplicate_canonical_export_paths() {
1073 let quoted = Arc::<Path>::from(Path::validate(r#"::foo::"bar""#).unwrap());
1074 let unquoted = Arc::<Path>::from(Path::validate("::foo::bar").unwrap());
1075
1076 let mut exports = BTreeMap::new();
1077 exports.insert(
1078 quoted.clone(),
1079 LibraryExport::Type(TypeExport { path: quoted, ty: Type::Felt }),
1080 );
1081 exports.insert(
1082 unquoted.clone(),
1083 LibraryExport::Type(TypeExport { path: unquoted, ty: Type::Felt }),
1084 );
1085
1086 let lib =
1087 Library::new(Arc::new(MastForest::new()), exports).expect("library must validate");
1088 let json = serde_json::to_string(&lib).expect("library serialization must succeed");
1089
1090 let err = serde_json::from_str::<Library>(&json).expect_err(
1091 "expected duplicate canonical export paths to be rejected during serde deserialization",
1092 );
1093 let message = alloc::format!("{err}");
1094 assert!(
1095 message.contains("duplicate canonical export path in library artifact"),
1096 "unexpected error: {err}"
1097 );
1098 }
1099
1100 #[cfg(feature = "serde")]
1101 #[test]
1102 fn serde_library_deserialization_rejects_malformed_quoted_procedure_leaf() {
1103 use miden_core::{
1104 mast::{BasicBlockNodeBuilder, MastForestContributor},
1105 operations::Operation,
1106 };
1107
1108 let mut mast_forest = MastForest::new();
1109 let node = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new())
1110 .add_to_forest(&mut mast_forest)
1111 .expect("must create MAST node");
1112 mast_forest.make_root(node);
1113
1114 let bad = Arc::<Path>::from(Path::validate(r#"::foo::"bad name""#).unwrap());
1115 let mut exports = BTreeMap::new();
1116 exports.insert(bad.clone(), LibraryExport::Procedure(ProcedureExport::new(node, bad)));
1117
1118 let lib = Library::new(Arc::new(mast_forest), exports).expect("library must validate");
1119 let json = serde_json::to_string(&lib).expect("library serialization must succeed");
1120
1121 let err = serde_json::from_str::<Library>(&json)
1122 .expect_err("expected malformed procedure export leaf name rejection during serde");
1123 let message = alloc::format!("{err}");
1124 assert!(
1125 message.contains("invalid procedure export leaf name"),
1126 "unexpected error: {err}"
1127 );
1128 }
1129
1130 #[cfg(feature = "serde")]
1131 #[test]
1132 fn serde_library_deserialization_rejects_malformed_quoted_constant_leaf() {
1133 let bad = Arc::<Path>::from(Path::validate(r#"::foo::"bad name""#).unwrap());
1134 let mut exports = BTreeMap::new();
1135 exports.insert(
1136 bad.clone(),
1137 LibraryExport::Constant(ConstantExport {
1138 path: bad,
1139 value: ConstantValue::Int(miden_debug_types::Span::unknown(
1140 crate::parser::IntValue::from(1u8),
1141 )),
1142 }),
1143 );
1144
1145 let lib =
1146 Library::new(Arc::new(MastForest::new()), exports).expect("library must validate");
1147 let json = serde_json::to_string(&lib).expect("library serialization must succeed");
1148
1149 let err = serde_json::from_str::<Library>(&json)
1150 .expect_err("expected malformed constant export leaf name rejection during serde");
1151 let message = alloc::format!("{err}");
1152 assert!(message.contains("invalid export leaf name"), "unexpected error: {err}");
1153 }
1154
1155 #[cfg(feature = "serde")]
1156 #[test]
1157 fn serde_library_deserialization_rejects_malformed_quoted_type_leaf() {
1158 let bad = Arc::<Path>::from(Path::validate(r#"::foo::"bad name""#).unwrap());
1159 let mut exports = BTreeMap::new();
1160 exports.insert(bad.clone(), LibraryExport::Type(TypeExport { path: bad, ty: Type::Felt }));
1161
1162 let lib =
1163 Library::new(Arc::new(MastForest::new()), exports).expect("library must validate");
1164 let json = serde_json::to_string(&lib).expect("library serialization must succeed");
1165
1166 let err = serde_json::from_str::<Library>(&json)
1167 .expect_err("expected malformed type export leaf name rejection during serde");
1168 let message = alloc::format!("{err}");
1169 assert!(message.contains("invalid export leaf name"), "unexpected error: {err}");
1170 }
1171}