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
478impl Library {
480 pub fn module_infos(&self) -> impl Iterator<Item = ModuleInfo> {
482 let mut modules_by_path: BTreeMap<Arc<Path>, ModuleInfo> = BTreeMap::new();
483
484 for export in self.exports.values() {
485 let module_name =
486 Arc::from(export.path().parent().unwrap().to_path_buf().into_boxed_path());
487 let module = modules_by_path
488 .entry(Arc::clone(&module_name))
489 .or_insert_with(|| ModuleInfo::new(module_name, None));
490 match export {
491 LibraryExport::Procedure(ProcedureExport { node, path, signature, attributes }) => {
492 let proc_digest = self.mast_forest[*node].digest();
493 let name = path.last().unwrap();
494 module.add_procedure(
495 ProcedureName::new(name).expect("valid procedure name"),
496 proc_digest,
497 signature.clone().map(Arc::new),
498 attributes.clone(),
499 );
500 },
501 LibraryExport::Constant(ConstantExport { path, value }) => {
502 let name = Ident::new(path.last().unwrap()).expect("valid identifier");
503 module.add_constant(name, value.clone());
504 },
505 LibraryExport::Type(TypeExport { path, ty }) => {
506 let name = Ident::new(path.last().unwrap()).expect("valid identifier");
507 module.add_type(name, ty.clone());
508 },
509 }
510 }
511
512 modules_by_path.into_values()
513 }
514}
515
516#[cfg(feature = "std")]
517impl Library {
518 pub const LIBRARY_EXTENSION: &'static str = "masl";
520
521 pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
527 let path = path.as_ref();
528
529 if let Some(dir) = path.parent() {
530 std::fs::create_dir_all(dir)?;
531 }
532
533 std::panic::catch_unwind(|| {
537 let mut file = std::fs::File::create(path)?;
538 self.write_into(&mut file);
539 Ok(())
540 })
541 .map_err(|p| match p.downcast::<std::io::Error>() {
542 Ok(err) => *err,
543 Err(err) => std::panic::resume_unwind(err),
544 })?
545 }
546
547 pub fn deserialize_from_file(
548 path: impl AsRef<std::path::Path>,
549 ) -> Result<Self, DeserializationError> {
550 use miden_core::utils::ReadAdapter;
551
552 let path = path.as_ref();
553 let mut file = std::fs::File::open(path).map_err(|err| {
554 DeserializationError::InvalidValue(format!(
555 "failed to open file at {}: {err}",
556 path.to_string_lossy()
557 ))
558 })?;
559 let mut adapter = ReadAdapter::new(&mut file);
560
561 Self::read_from(&mut adapter)
562 }
563}
564
565#[derive(Debug, Clone, PartialEq, Eq)]
575#[cfg_attr(feature = "serde", derive(Deserialize))]
576#[cfg_attr(feature = "serde", serde(try_from = "Arc<Library>"))]
577pub struct KernelLibrary {
578 #[cfg_attr(feature = "serde", serde(skip))]
579 kernel: Kernel,
580 #[cfg_attr(feature = "serde", serde(skip))]
581 kernel_info: ModuleInfo,
582 library: Arc<Library>,
583}
584
585#[cfg(feature = "serde")]
586impl Serialize for KernelLibrary {
587 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
588 where
589 S: serde::Serializer,
590 {
591 Library::serialize(&self.library, serializer)
592 }
593}
594
595impl AsRef<Library> for KernelLibrary {
596 #[inline(always)]
597 fn as_ref(&self) -> &Library {
598 &self.library
599 }
600}
601
602impl KernelLibrary {
603 fn try_from_library(library: Library) -> Result<Self, DeserializationError> {
604 Self::try_from(Arc::new(library)).map_err(|err| {
605 DeserializationError::InvalidValue(format!(
606 "Failed to deserialize kernel library: {err}"
607 ))
608 })
609 }
610
611 pub fn kernel(&self) -> &Kernel {
613 &self.kernel
614 }
615
616 pub fn mast_forest(&self) -> &Arc<MastForest> {
618 self.library.mast_forest()
619 }
620
621 pub fn into_parts(self) -> (Kernel, ModuleInfo, Arc<MastForest>) {
623 (self.kernel, self.kernel_info, self.library.mast_forest().clone())
624 }
625
626 pub fn read_from_unchecked<R: ByteReader>(
636 source: &mut R,
637 ) -> Result<Self, DeserializationError> {
638 let library = Library::read_from_unchecked(source)?;
639 Self::try_from_library(library)
640 }
641
642 pub fn read_from_bytes_unchecked(bytes: &[u8]) -> Result<Self, DeserializationError> {
646 let mut source = SliceReader::new(bytes);
647 Self::read_from_unchecked(&mut source)
648 }
649}
650
651impl TryFrom<Arc<Library>> for KernelLibrary {
652 type Error = LibraryError;
653
654 fn try_from(library: Arc<Library>) -> Result<Self, Self::Error> {
655 let kernel_path = Arc::from(Path::kernel_path().to_path_buf().into_boxed_path());
656 let mut proc_digests = Vec::with_capacity(library.exports.len());
657
658 let mut kernel_module = ModuleInfo::new(Arc::clone(&kernel_path), None);
659
660 for export in library.exports.values() {
661 match export {
662 LibraryExport::Procedure(export) => {
663 if !export.path.is_in_kernel() {
665 return Err(LibraryError::InvalidKernelExport {
666 procedure_path: export.path.clone(),
667 });
668 }
669
670 let proc_digest = library.mast_forest[export.node].digest();
671 proc_digests.push(proc_digest);
672 kernel_module.add_procedure(
673 ProcedureName::new(export.path.last().unwrap())
674 .expect("valid procedure name"),
675 proc_digest,
676 export.signature.clone().map(Arc::new),
677 export.attributes.clone(),
678 );
679 },
680 LibraryExport::Constant(export) => {
681 if export.path.is_in_kernel() {
683 let name =
684 Ident::new(export.path.last().unwrap()).expect("valid identifier");
685 kernel_module.add_constant(name, export.value.clone());
686 }
687 },
688 LibraryExport::Type(export) => {
689 if export.path.is_in_kernel() {
691 let name =
692 Ident::new(export.path.last().unwrap()).expect("valid identifier");
693 kernel_module.add_type(name, export.ty.clone());
694 }
695 },
696 }
697 }
698
699 if proc_digests.is_empty() {
700 return Err(LibraryError::NoExport);
701 }
702
703 let kernel = Kernel::new(&proc_digests).map_err(LibraryError::KernelConversion)?;
704
705 Ok(Self {
706 kernel,
707 kernel_info: kernel_module,
708 library,
709 })
710 }
711}
712
713#[cfg(feature = "std")]
714impl KernelLibrary {
715 pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
717 self.library.write_to_file(path)
718 }
719}
720
721fn export_raw_leaf(path: &Path) -> Result<&str, String> {
725 match path.components().next_back() {
726 Some(Ok(PathComponent::Normal(leaf))) => Ok(leaf),
727 Some(Err(err)) => Err(format!("invalid export path '{path}': {err}")),
728 Some(Ok(PathComponent::Root)) | None => {
729 Err(format!("invalid export path (missing export leaf): '{path}'"))
730 },
731 }
732}
733
734fn canonicalize_export_path(path: &Path) -> Result<Arc<Path>, String> {
735 let canonical = path
736 .to_path_buf()
737 .canonicalize()
738 .map_err(|err| format!("invalid export path '{path}': {err}"))?;
739 Ok(Arc::<Path>::from(canonical.into_boxed_path()))
740}
741
742fn normalize_export_for_deserialization(
743 mut export: LibraryExport,
744) -> Result<(Arc<Path>, LibraryExport), String> {
745 let canonical_path = canonicalize_export_path(export.path().as_ref())?;
746 let leaf = export_raw_leaf(canonical_path.as_ref())?;
747
748 match &export {
749 LibraryExport::Procedure(_) => {
750 ProcedureName::new(leaf).map_err(|err| {
751 format!(
752 "invalid procedure export leaf name '{leaf}' in path '{canonical_path}': {err}"
753 )
754 })?;
755 },
756 LibraryExport::Constant(_) | LibraryExport::Type(_) => {
757 Ident::new(leaf).map_err(|err| {
758 format!("invalid export leaf name '{leaf}' in path '{canonical_path}': {err}")
759 })?;
760 },
761 }
762
763 match &mut export {
764 LibraryExport::Procedure(export) => export.path = canonical_path.clone(),
765 LibraryExport::Constant(export) => export.path = canonical_path.clone(),
766 LibraryExport::Type(export) => export.path = canonical_path.clone(),
767 }
768
769 Ok((canonical_path, export))
770}
771
772impl Serializable for Library {
774 fn write_into<W: ByteWriter>(&self, target: &mut W) {
775 let Self { digest: _, exports, mast_forest } = self;
776
777 mast_forest.write_into(target);
778
779 target.write_usize(exports.len());
780 for export in exports.values() {
781 export.write_into(target);
782 }
783 }
784}
785
786impl Deserializable for Library {
788 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
789 let mast_forest = Self::read_mast_forest(source, true)?;
790 Self::read_from_with_mast_forest(source, mast_forest)
791 }
792}
793
794#[cfg(feature = "serde")]
795impl Serialize for Library {
796 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
797 where
798 S: serde::Serializer,
799 {
800 use serde::ser::SerializeStruct;
801
802 struct LibraryExports<'a>(&'a BTreeMap<Arc<Path>, LibraryExport>);
803 impl Serialize for LibraryExports<'_> {
804 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
805 where
806 S: serde::Serializer,
807 {
808 use serde::ser::SerializeSeq;
809 let mut serializer = serializer.serialize_seq(Some(self.0.len()))?;
810 for elem in self.0.values() {
811 serializer.serialize_element(elem)?;
812 }
813 serializer.end()
814 }
815 }
816
817 let Self { digest: _, exports, mast_forest } = self;
818
819 let mut serializer = serializer.serialize_struct("Library", 2)?;
820 serializer.serialize_field("mast_forest", mast_forest)?;
821 serializer.serialize_field("exports", &LibraryExports(exports))?;
822 serializer.end()
823 }
824}
825
826#[cfg(feature = "serde")]
827impl<'de> Deserialize<'de> for Library {
828 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
829 where
830 D: serde::Deserializer<'de>,
831 {
832 use serde::de::Visitor;
833
834 #[derive(Deserialize)]
835 #[serde(field_identifier, rename_all = "snake_case")]
836 enum Field {
837 MastForest,
838 Exports,
839 }
840
841 struct LibraryVisitor;
842
843 impl LibraryVisitor {
844 fn normalize_exports<E>(
845 items: Vec<LibraryExport>,
846 ) -> Result<BTreeMap<Arc<Path>, LibraryExport>, E>
847 where
848 E: serde::de::Error,
849 {
850 let mut exports = BTreeMap::new();
851 for export in items {
852 let (path, export) = normalize_export_for_deserialization(export)
853 .map_err(serde::de::Error::custom)?;
854 if exports.insert(path.clone(), export).is_some() {
855 return Err(serde::de::Error::custom(format!(
856 "duplicate canonical export path in library artifact: '{path}'"
857 )));
858 }
859 }
860
861 Ok(exports)
862 }
863 }
864
865 impl<'de> Visitor<'de> for LibraryVisitor {
866 type Value = Library;
867
868 fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
869 formatter.write_str("struct Library")
870 }
871
872 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
873 where
874 A: serde::de::SeqAccess<'de>,
875 {
876 let mast_forest = seq
877 .next_element()?
878 .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
879 let export_items: Vec<LibraryExport> = seq
880 .next_element()?
881 .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
882 let exports = Self::normalize_exports(export_items)?;
883 Library::new(mast_forest, exports).map_err(serde::de::Error::custom)
884 }
885
886 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
887 where
888 A: serde::de::MapAccess<'de>,
889 {
890 let mut mast_forest = None;
891 let mut exports = None;
892 while let Some(key) = map.next_key()? {
893 match key {
894 Field::MastForest => {
895 if mast_forest.is_some() {
896 return Err(serde::de::Error::duplicate_field("mast_forest"));
897 }
898 mast_forest = Some(map.next_value()?);
899 },
900 Field::Exports => {
901 if exports.is_some() {
902 return Err(serde::de::Error::duplicate_field("exports"));
903 }
904 let items: Vec<LibraryExport> = map.next_value()?;
905 exports = Some(Self::normalize_exports(items)?);
906 },
907 }
908 }
909 let mast_forest =
910 mast_forest.ok_or_else(|| serde::de::Error::missing_field("mast_forest"))?;
911 let exports = exports.ok_or_else(|| serde::de::Error::missing_field("exports"))?;
912 Library::new(mast_forest, exports).map_err(serde::de::Error::custom)
913 }
914 }
915
916 deserializer.deserialize_struct("Library", &["mast_forest", "exports"], LibraryVisitor)
917 }
918}
919
920impl Serializable for KernelLibrary {
922 fn write_into<W: ByteWriter>(&self, target: &mut W) {
923 let Self { kernel: _, kernel_info: _, library } = self;
924
925 library.write_into(target);
926 }
927}
928
929impl Deserializable for KernelLibrary {
931 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
932 let library = Library::read_from(source)?;
933 Self::try_from_library(library)
934 }
935}
936
937impl Serializable for LibraryExport {
939 fn write_into<W: ByteWriter>(&self, target: &mut W) {
940 match self {
941 LibraryExport::Procedure(ProcedureExport {
942 node,
943 path: name,
944 signature,
945 attributes,
946 }) => {
947 target.write_u8(0);
948 name.write_into(target);
949 target.write_u32(u32::from(*node));
950 if let Some(sig) = signature {
951 target.write_bool(true);
952 sig.write_into(target);
953 } else {
954 target.write_bool(false);
955 }
956 attributes.write_into(target);
957 },
958 LibraryExport::Constant(ConstantExport { path: name, value }) => {
959 target.write_u8(1);
960 name.write_into(target);
961 value.write_into(target);
962 },
963 LibraryExport::Type(TypeExport { path: name, ty }) => {
964 target.write_u8(2);
965 name.write_into(target);
966 ty.write_into(target);
967 },
968 }
969 }
970}
971
972#[cfg(feature = "arbitrary")]
973impl Arbitrary for Library {
974 type Parameters = ();
975
976 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
977 use miden_core::{
978 mast::{BasicBlockNodeBuilder, MastForestContributor},
979 operations::Operation,
980 };
981 use proptest::prelude::*;
982
983 prop::collection::vec(any::<LibraryExport>(), 1..5)
984 .prop_map(|exports| {
985 let mut exports =
986 BTreeMap::from_iter(exports.into_iter().map(|export| (export.path(), export)));
987 let mut mast_forest = MastForest::new();
989 let mut nodes = Vec::new();
990
991 for export in exports.values() {
992 if let LibraryExport::Procedure(export) = export {
993 let node_id = BasicBlockNodeBuilder::new(
994 vec![Operation::Add, Operation::Mul],
995 Vec::new(),
996 )
997 .add_to_forest(&mut mast_forest)
998 .unwrap();
999 nodes.push((export.node, node_id));
1000 }
1001 }
1002
1003 let mut procedure_exports = 0;
1005 for export in exports.values_mut() {
1006 match export {
1007 LibraryExport::Procedure(export) => {
1008 procedure_exports += 1;
1009 if let Some(&(_, actual_node_id)) =
1011 nodes.iter().find(|(original_id, _)| *original_id == export.node)
1012 {
1013 export.node = actual_node_id;
1014 } else {
1015 if let Some(&(_, first_node_id)) = nodes.first() {
1018 export.node = first_node_id;
1019 } else {
1020 panic!("No nodes created for exports");
1023 }
1024 }
1025 },
1026 LibraryExport::Constant(_) | LibraryExport::Type(_) => (),
1027 }
1028 }
1029
1030 let mut node_ids = Vec::with_capacity(procedure_exports);
1031 for export in exports.values() {
1032 if let LibraryExport::Procedure(export) = export {
1033 mast_forest.make_root(export.node);
1035 node_ids.push(export.node);
1037 }
1038 }
1039
1040 let digest = mast_forest.compute_nodes_commitment(&node_ids);
1042
1043 let mast_forest = Arc::new(mast_forest);
1044 Library { digest, exports, mast_forest }
1045 })
1046 .boxed()
1047 }
1048
1049 type Strategy = BoxedStrategy<Self>;
1050}
1051
1052#[cfg(test)]
1053mod tests {
1054 #[cfg(feature = "serde")]
1055 use super::*;
1056
1057 #[cfg(feature = "serde")]
1058 #[test]
1059 fn serde_library_deserialization_rejects_duplicate_canonical_export_paths() {
1060 let quoted = Arc::<Path>::from(Path::validate(r#"::foo::"bar""#).unwrap());
1061 let unquoted = Arc::<Path>::from(Path::validate("::foo::bar").unwrap());
1062
1063 let mut exports = BTreeMap::new();
1064 exports.insert(
1065 quoted.clone(),
1066 LibraryExport::Type(TypeExport { path: quoted, ty: Type::Felt }),
1067 );
1068 exports.insert(
1069 unquoted.clone(),
1070 LibraryExport::Type(TypeExport { path: unquoted, ty: Type::Felt }),
1071 );
1072
1073 let lib =
1074 Library::new(Arc::new(MastForest::new()), exports).expect("library must validate");
1075 let json = serde_json::to_string(&lib).expect("library serialization must succeed");
1076
1077 let err = serde_json::from_str::<Library>(&json).expect_err(
1078 "expected duplicate canonical export paths to be rejected during serde deserialization",
1079 );
1080 let message = alloc::format!("{err}");
1081 assert!(
1082 message.contains("duplicate canonical export path in library artifact"),
1083 "unexpected error: {err}"
1084 );
1085 }
1086
1087 #[cfg(feature = "serde")]
1088 #[test]
1089 fn serde_library_deserialization_rejects_malformed_quoted_procedure_leaf() {
1090 use miden_core::{
1091 mast::{BasicBlockNodeBuilder, MastForestContributor},
1092 operations::Operation,
1093 };
1094
1095 let mut mast_forest = MastForest::new();
1096 let node = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new())
1097 .add_to_forest(&mut mast_forest)
1098 .expect("must create MAST node");
1099 mast_forest.make_root(node);
1100
1101 let bad = Arc::<Path>::from(Path::validate(r#"::foo::"bad name""#).unwrap());
1102 let mut exports = BTreeMap::new();
1103 exports.insert(bad.clone(), LibraryExport::Procedure(ProcedureExport::new(node, bad)));
1104
1105 let lib = Library::new(Arc::new(mast_forest), exports).expect("library must validate");
1106 let json = serde_json::to_string(&lib).expect("library serialization must succeed");
1107
1108 let err = serde_json::from_str::<Library>(&json)
1109 .expect_err("expected malformed procedure export leaf name rejection during serde");
1110 let message = alloc::format!("{err}");
1111 assert!(
1112 message.contains("invalid procedure export leaf name"),
1113 "unexpected error: {err}"
1114 );
1115 }
1116
1117 #[cfg(feature = "serde")]
1118 #[test]
1119 fn serde_library_deserialization_rejects_malformed_quoted_constant_leaf() {
1120 let bad = Arc::<Path>::from(Path::validate(r#"::foo::"bad name""#).unwrap());
1121 let mut exports = BTreeMap::new();
1122 exports.insert(
1123 bad.clone(),
1124 LibraryExport::Constant(ConstantExport {
1125 path: bad,
1126 value: ConstantValue::Int(miden_debug_types::Span::unknown(
1127 crate::parser::IntValue::from(1u8),
1128 )),
1129 }),
1130 );
1131
1132 let lib =
1133 Library::new(Arc::new(MastForest::new()), exports).expect("library must validate");
1134 let json = serde_json::to_string(&lib).expect("library serialization must succeed");
1135
1136 let err = serde_json::from_str::<Library>(&json)
1137 .expect_err("expected malformed constant export leaf name rejection during serde");
1138 let message = alloc::format!("{err}");
1139 assert!(message.contains("invalid export leaf name"), "unexpected error: {err}");
1140 }
1141
1142 #[cfg(feature = "serde")]
1143 #[test]
1144 fn serde_library_deserialization_rejects_malformed_quoted_type_leaf() {
1145 let bad = Arc::<Path>::from(Path::validate(r#"::foo::"bad name""#).unwrap());
1146 let mut exports = BTreeMap::new();
1147 exports.insert(bad.clone(), LibraryExport::Type(TypeExport { path: bad, ty: Type::Felt }));
1148
1149 let lib =
1150 Library::new(Arc::new(MastForest::new()), exports).expect("library must validate");
1151 let json = serde_json::to_string(&lib).expect("library serialization must succeed");
1152
1153 let err = serde_json::from_str::<Library>(&json)
1154 .expect_err("expected malformed type export leaf name rejection during serde");
1155 let message = alloc::format!("{err}");
1156 assert!(message.contains("invalid export leaf name"), "unexpected error: {err}");
1157 }
1158}