miden_assembly_syntax/library/
mod.rs

1use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec};
2
3use miden_core::{
4    AdviceMap, Kernel, Word,
5    mast::{MastForest, MastNodeId},
6    utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
7};
8use midenc_hir_type::{FunctionType, Type};
9use smallvec::SmallVec;
10
11use crate::ast::QualifiedProcedureName;
12
13mod error;
14mod module;
15mod namespace;
16mod path;
17
18pub use module::{ModuleInfo, ProcedureInfo};
19pub use semver::{Error as VersionError, Version};
20
21pub use self::{
22    error::LibraryError,
23    namespace::{LibraryNamespace, LibraryNamespaceError},
24    path::{LibraryPath, LibraryPathComponent, PathError},
25};
26
27// LIBRARY EXPORT
28// ================================================================================================
29
30/// Metadata about a procedure exported by the interface of a [Library]
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct LibraryExport {
33    /// The id of the MAST root node of the exported procedure
34    pub node: MastNodeId,
35    /// The fully-qualified name of the exported procedure
36    pub name: QualifiedProcedureName,
37    /// The type signature of the exported procedure, if known
38    pub signature: Option<FunctionType>,
39}
40
41impl LibraryExport {
42    /// Create a new [LibraryExport] representing the export of `node` with `name`
43    pub fn new(node: MastNodeId, name: QualifiedProcedureName) -> Self {
44        Self { node, name, signature: None }
45    }
46
47    /// Specify the type signature and ABI of this export
48    pub fn with_signature(mut self, signature: FunctionType) -> Self {
49        self.signature = Some(signature);
50        self
51    }
52}
53
54// LIBRARY
55// ================================================================================================
56
57/// Represents a library where all modules were compiled into a [`MastForest`].
58///
59/// A library exports a set of one or more procedures. Currently, all exported procedures belong
60/// to the same top-level namespace.
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct Library {
63    /// The content hash of this library, formed by hashing the roots of all exports in
64    /// lexicographical order (by digest, not procedure name)
65    digest: Word,
66    /// A map between procedure paths and the corresponding procedure metadata in the MAST forest.
67    /// Multiple paths can map to the same root, and also, some roots may not be associated with
68    /// any paths.
69    ///
70    /// Note that we use `MastNodeId` as an identifier for procedures instead of MAST root, since 2
71    /// different procedures with the same MAST root can be different due to the decorators they
72    /// contain. However, note that `MastNodeId` is also not a unique identifier for procedures; if
73    /// the procedures have the same MAST root and decorators, they will have the same
74    /// `MastNodeId`.
75    exports: BTreeMap<QualifiedProcedureName, LibraryExport>,
76    /// The MAST forest underlying this library.
77    mast_forest: Arc<MastForest>,
78}
79
80impl AsRef<Library> for Library {
81    #[inline(always)]
82    fn as_ref(&self) -> &Library {
83        self
84    }
85}
86
87// ------------------------------------------------------------------------------------------------
88/// Constructors
89impl Library {
90    /// Constructs a new [`Library`] from the provided MAST forest and a set of exports.
91    ///
92    /// # Errors
93    /// Returns an error if the set of exports is empty.
94    /// Returns an error if any of the specified exports do not have a corresponding procedure root
95    /// in the provided MAST forest.
96    pub fn new(
97        mast_forest: Arc<MastForest>,
98        exports: BTreeMap<QualifiedProcedureName, LibraryExport>,
99    ) -> Result<Self, LibraryError> {
100        if exports.is_empty() {
101            return Err(LibraryError::NoExport);
102        }
103        for LibraryExport { name, node, .. } in exports.values() {
104            if !mast_forest.is_procedure_root(*node) {
105                return Err(LibraryError::NoProcedureRootForExport {
106                    procedure_path: name.clone(),
107                });
108            }
109        }
110
111        let digest =
112            mast_forest.compute_nodes_commitment(exports.values().map(|export| &export.node));
113
114        Ok(Self { digest, exports, mast_forest })
115    }
116
117    /// Produces a new library with the existing [`MastForest`] and where all key/values in the
118    /// provided advice map are added to the internal advice map.
119    pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
120        let mut mast_forest = (*self.mast_forest).clone();
121        mast_forest.advice_map_mut().extend(advice_map);
122        Self {
123            mast_forest: Arc::new(mast_forest),
124            ..self
125        }
126    }
127}
128
129// ------------------------------------------------------------------------------------------------
130/// Public accessors
131impl Library {
132    /// Returns the [Word] representing the content hash of this library
133    pub fn digest(&self) -> &Word {
134        &self.digest
135    }
136
137    /// Returns the fully qualified name and metadata of all procedures exported by the library.
138    pub fn exports(&self) -> impl Iterator<Item = &LibraryExport> {
139        self.exports.values()
140    }
141
142    /// Returns the number of exports in this library.
143    pub fn num_exports(&self) -> usize {
144        self.exports.len()
145    }
146
147    /// Returns a MAST node ID associated with the specified exported procedure.
148    ///
149    /// # Panics
150    /// Panics if the specified procedure is not exported from this library.
151    pub fn get_export_node_id(&self, proc_name: &QualifiedProcedureName) -> MastNodeId {
152        self.exports
153            .get(proc_name)
154            .expect("procedure not exported from the library")
155            .node
156    }
157
158    /// Returns true if the specified exported procedure is re-exported from a dependency.
159    pub fn is_reexport(&self, proc_name: &QualifiedProcedureName) -> bool {
160        self.exports
161            .get(proc_name)
162            .map(|export| self.mast_forest[export.node].is_external())
163            .unwrap_or(false)
164    }
165
166    /// Returns a reference to the inner [`MastForest`].
167    pub fn mast_forest(&self) -> &Arc<MastForest> {
168        &self.mast_forest
169    }
170
171    /// Returns the digest of the procedure with the specified name, or `None` if it was not found
172    /// in the library or its library path is malformed.
173    pub fn get_procedure_root_by_name(
174        &self,
175        proc_name: impl TryInto<QualifiedProcedureName>,
176    ) -> Option<Word> {
177        if let Ok(qualified_proc_name) = proc_name.try_into() {
178            let export = self.exports.get(&qualified_proc_name);
179            export.map(|e| self.mast_forest()[e.node].digest())
180        } else {
181            None
182        }
183    }
184}
185
186/// Conversions
187impl Library {
188    /// Returns an iterator over the module infos of the library.
189    pub fn module_infos(&self) -> impl Iterator<Item = ModuleInfo> {
190        let mut modules_by_path: BTreeMap<LibraryPath, ModuleInfo> = BTreeMap::new();
191
192        for LibraryExport { node, name, signature } in self.exports.values() {
193            modules_by_path
194                .entry(name.module.clone())
195                .and_modify(|compiled_module| {
196                    let proc_digest = self.mast_forest[*node].digest();
197                    compiled_module.add_procedure(
198                        name.name.clone(),
199                        proc_digest,
200                        signature.clone().map(Arc::new),
201                    );
202                })
203                .or_insert_with(|| {
204                    let mut module_info = ModuleInfo::new(name.module.clone());
205
206                    let proc_digest = self.mast_forest[*node].digest();
207                    module_info.add_procedure(
208                        name.name.clone(),
209                        proc_digest,
210                        signature.clone().map(Arc::new),
211                    );
212
213                    module_info
214                });
215        }
216
217        modules_by_path.into_values()
218    }
219}
220
221#[cfg(feature = "std")]
222impl Library {
223    /// File extension for the Assembly Library.
224    pub const LIBRARY_EXTENSION: &'static str = "masl";
225
226    /// Write the library to a target file
227    ///
228    /// NOTE: It is up to the caller to use the correct file extension, but there is no
229    /// specific requirement that the extension be set, or the same as
230    /// [`Self::LIBRARY_EXTENSION`].
231    pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
232        let path = path.as_ref();
233
234        if let Some(dir) = path.parent() {
235            std::fs::create_dir_all(dir)?;
236        }
237
238        // NOTE: We catch panics due to i/o errors here due to the fact that the ByteWriter
239        // trait does not provide fallible APIs, so WriteAdapter will panic if the underlying
240        // writes fail. This needs to be addressed in winterfell at some point
241        std::panic::catch_unwind(|| {
242            let mut file = std::fs::File::create(path)?;
243            self.write_into(&mut file);
244            Ok(())
245        })
246        .map_err(|p| {
247            match p.downcast::<std::io::Error>() {
248                // SAFETY: It is guaranteed safe to read Box<std::io::Error>
249                Ok(err) => unsafe { core::ptr::read(&*err) },
250                Err(err) => std::panic::resume_unwind(err),
251            }
252        })?
253    }
254
255    pub fn deserialize_from_file(
256        path: impl AsRef<std::path::Path>,
257    ) -> Result<Self, DeserializationError> {
258        use miden_core::utils::ReadAdapter;
259
260        let path = path.as_ref();
261        let mut file = std::fs::File::open(path).map_err(|err| {
262            DeserializationError::InvalidValue(format!(
263                "failed to open file at {}: {err}",
264                path.to_string_lossy()
265            ))
266        })?;
267        let mut adapter = ReadAdapter::new(&mut file);
268
269        Self::read_from(&mut adapter)
270    }
271}
272
273// KERNEL LIBRARY
274// ================================================================================================
275
276/// Represents a library containing a Miden VM kernel.
277///
278/// This differs from the regular [Library] as follows:
279/// - All exported procedures must be exported directly from the kernel namespace (i.e., `$kernel`).
280/// - There must be at least one exported procedure.
281/// - The number of exported procedures cannot exceed [Kernel::MAX_NUM_PROCEDURES] (i.e., 256).
282#[derive(Debug, Clone, PartialEq, Eq)]
283pub struct KernelLibrary {
284    kernel: Kernel,
285    kernel_info: ModuleInfo,
286    library: Library,
287}
288
289impl AsRef<Library> for KernelLibrary {
290    #[inline(always)]
291    fn as_ref(&self) -> &Library {
292        &self.library
293    }
294}
295
296impl KernelLibrary {
297    /// Returns the [Kernel] for this kernel library.
298    pub fn kernel(&self) -> &Kernel {
299        &self.kernel
300    }
301
302    /// Returns a reference to the inner [`MastForest`].
303    pub fn mast_forest(&self) -> &Arc<MastForest> {
304        self.library.mast_forest()
305    }
306
307    /// Destructures this kernel library into individual parts.
308    pub fn into_parts(self) -> (Kernel, ModuleInfo, Arc<MastForest>) {
309        (self.kernel, self.kernel_info, self.library.mast_forest)
310    }
311}
312
313impl TryFrom<Library> for KernelLibrary {
314    type Error = LibraryError;
315
316    fn try_from(library: Library) -> Result<Self, Self::Error> {
317        let kernel_path = LibraryPath::from(LibraryNamespace::Kernel);
318        let mut proc_digests = Vec::with_capacity(library.exports.len());
319
320        let mut kernel_module = ModuleInfo::new(kernel_path.clone());
321
322        for export in library.exports.values() {
323            // make sure all procedures are exported only from the kernel root
324            if export.name.module != kernel_path {
325                return Err(LibraryError::InvalidKernelExport {
326                    procedure_path: export.name.clone(),
327                });
328            }
329
330            let proc_digest = library.mast_forest[export.node].digest();
331            proc_digests.push(proc_digest);
332            kernel_module.add_procedure(
333                export.name.name.clone(),
334                proc_digest,
335                export.signature.clone().map(Arc::new),
336            );
337        }
338
339        let kernel = Kernel::new(&proc_digests).map_err(LibraryError::KernelConversion)?;
340
341        Ok(Self {
342            kernel,
343            kernel_info: kernel_module,
344            library,
345        })
346    }
347}
348
349#[cfg(feature = "std")]
350impl KernelLibrary {
351    /// Write the library to a target file
352    pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
353        self.library.write_to_file(path)
354    }
355}
356
357// LIBRARY SERIALIZATION
358// ================================================================================================
359
360/// NOTE: Serialization of libraries is likely to be deprecated in a future release
361impl Serializable for Library {
362    fn write_into<W: ByteWriter>(&self, target: &mut W) {
363        let Self { digest: _, exports, mast_forest } = self;
364
365        mast_forest.write_into(target);
366
367        target.write_usize(exports.len());
368        for LibraryExport { node, name, signature } in exports.values() {
369            name.module.write_into(target);
370            name.name.write_into(target);
371            target.write_u32(node.as_u32());
372            if let Some(sig) = signature {
373                target.write_bool(true);
374                FunctionTypeSerializer(sig).write_into(target);
375            } else {
376                target.write_bool(false);
377            }
378        }
379    }
380}
381
382/// NOTE: Serialization of libraries is likely to be deprecated in a future release
383impl Deserializable for Library {
384    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
385        let mast_forest = Arc::new(MastForest::read_from(source)?);
386
387        let num_exports = source.read_usize()?;
388        if num_exports == 0 {
389            return Err(DeserializationError::InvalidValue(String::from("No exported procedures")));
390        };
391        let mut exports = BTreeMap::new();
392        for _ in 0..num_exports {
393            let proc_module = source.read()?;
394            let proc_name = source.read()?;
395            let proc_name = QualifiedProcedureName::new(proc_module, proc_name);
396            let node = MastNodeId::from_u32_safe(source.read_u32()?, &mast_forest)?;
397            let signature = if source.read_bool()? {
398                Some(FunctionTypeDeserializer::read_from(source)?.0)
399            } else {
400                None
401            };
402            let export = LibraryExport { node, name: proc_name.clone(), signature };
403
404            exports.insert(proc_name, export);
405        }
406
407        let digest = mast_forest.compute_nodes_commitment(exports.values().map(|e| &e.node));
408
409        Ok(Self { digest, exports, mast_forest })
410    }
411}
412
413/// NOTE: Serialization of libraries is likely to be deprecated in a future release
414impl Serializable for KernelLibrary {
415    fn write_into<W: ByteWriter>(&self, target: &mut W) {
416        let Self { kernel: _, kernel_info: _, library } = self;
417
418        library.write_into(target);
419    }
420}
421
422/// NOTE: Serialization of libraries is likely to be deprecated in a future release
423impl Deserializable for KernelLibrary {
424    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
425        let library = Library::read_from(source)?;
426
427        Self::try_from(library).map_err(|err| {
428            DeserializationError::InvalidValue(format!(
429                "Failed to deserialize kernel library: {err}"
430            ))
431        })
432    }
433}
434
435/// A wrapper type for [FunctionType] that provides serialization support via the winter-utils
436/// serializer.
437///
438/// This is a temporary implementation to allow type information to be serialized with libraries,
439/// but in a future release we'll either rely on the `serde` serialization for these types, or
440/// provide the serialization implementation in midenc-hir-type instead
441pub struct FunctionTypeSerializer<'a>(pub &'a FunctionType);
442
443impl Serializable for FunctionTypeSerializer<'_> {
444    fn write_into<W: ByteWriter>(&self, target: &mut W) {
445        target.write_u8(self.0.abi as u8);
446        target.write_usize(self.0.params().len());
447        target.write_many(self.0.params().iter().map(TypeSerializer));
448        target.write_usize(self.0.results().len());
449        target.write_many(self.0.results().iter().map(TypeSerializer));
450    }
451}
452
453/// A wrapper type for [FunctionType] that provides deserialization support via the winter-utils
454/// serializer.
455///
456/// This is a temporary implementation to allow type information to be serialized with libraries,
457/// but in a future release we'll either rely on the `serde` serialization for these types, or
458/// provide the serialization implementation in midenc-hir-type instead
459pub struct FunctionTypeDeserializer(pub FunctionType);
460
461impl Deserializable for FunctionTypeDeserializer {
462    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
463        use midenc_hir_type::CallConv;
464
465        let abi = match source.read_u8()? {
466            0 => CallConv::Fast,
467            1 => CallConv::SystemV,
468            2 => CallConv::Wasm,
469            3 => CallConv::CanonLift,
470            4 => CallConv::CanonLower,
471            5 => CallConv::Kernel,
472            invalid => {
473                return Err(DeserializationError::InvalidValue(format!(
474                    "invalid CallConv tag: {invalid}"
475                )));
476            },
477        };
478
479        let arity = source.read_usize()?;
480        let mut params = SmallVec::<[Type; 4]>::with_capacity(arity);
481        for _ in 0..arity {
482            let ty = TypeDeserializer::read_from(source)?.0;
483            params.push(ty);
484        }
485
486        let num_results = source.read_usize()?;
487        let mut results = SmallVec::<[Type; 1]>::with_capacity(num_results);
488        for _ in 0..num_results {
489            let ty = TypeDeserializer::read_from(source)?.0;
490            results.push(ty);
491        }
492
493        Ok(Self(FunctionType { abi, params, results }))
494    }
495}
496
497/// A wrapper type for [Type] that provides serialization support via the winter-utils serializer.
498///
499/// This is a temporary implementation to allow type information to be serialized with libraries,
500/// but in a future release we'll either rely on the `serde` serialization for these types, or
501/// provide the serialization implementation in midenc-hir-type instead
502pub struct TypeSerializer<'a>(pub &'a Type);
503
504impl Serializable for TypeSerializer<'_> {
505    fn write_into<W: ByteWriter>(&self, target: &mut W) {
506        use midenc_hir_type::{AddressSpace, TypeRepr};
507
508        match self.0 {
509            Type::Unknown => target.write_u8(0),
510            Type::Never => target.write_u8(1),
511            Type::I1 => target.write_u8(2),
512            Type::I8 => target.write_u8(3),
513            Type::U8 => target.write_u8(4),
514            Type::I16 => target.write_u8(5),
515            Type::U16 => target.write_u8(6),
516            Type::I32 => target.write_u8(7),
517            Type::U32 => target.write_u8(8),
518            Type::I64 => target.write_u8(9),
519            Type::U64 => target.write_u8(10),
520            Type::I128 => target.write_u8(11),
521            Type::U128 => target.write_u8(12),
522            Type::U256 => target.write_u8(13),
523            Type::F64 => target.write_u8(14),
524            Type::Felt => target.write_u8(15),
525            Type::Ptr(ty) => {
526                target.write_u8(16);
527                match ty.addrspace {
528                    AddressSpace::Byte => target.write_u8(0),
529                    AddressSpace::Element => target.write_u8(1),
530                }
531                TypeSerializer(&ty.pointee).write_into(target);
532            },
533            Type::Struct(ty) => {
534                target.write_u8(17);
535                match ty.repr() {
536                    TypeRepr::Default => target.write_u8(0),
537                    TypeRepr::Align(align) => {
538                        target.write_u8(1);
539                        target.write_u16(align.get());
540                    },
541                    TypeRepr::Packed(align) => {
542                        target.write_u8(2);
543                        target.write_u16(align.get());
544                    },
545                    TypeRepr::Transparent => target.write_u8(3),
546                }
547                target.write_u8(ty.len() as u8);
548                for field in ty.fields() {
549                    TypeSerializer(&field.ty).write_into(target);
550                }
551            },
552            Type::Array(ty) => {
553                target.write_u8(18);
554                target.write_usize(ty.len);
555                TypeSerializer(&ty.ty).write_into(target);
556            },
557            Type::List(ty) => {
558                target.write_u8(19);
559                TypeSerializer(ty).write_into(target);
560            },
561            Type::Function(ty) => {
562                target.write_u8(20);
563                FunctionTypeSerializer(ty).write_into(target);
564            },
565        }
566    }
567}
568
569/// A wrapper type for [Type] that provides deserialization support via the winter-utils serializer.
570///
571/// This is a temporary implementation to allow type information to be serialized with libraries,
572/// but in a future release we'll either rely on the `serde` serialization for these types, or
573/// provide the serialization implementation in midenc-hir-type instead
574pub struct TypeDeserializer(pub Type);
575
576impl Deserializable for TypeDeserializer {
577    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
578        use alloc::string::ToString;
579        use core::num::NonZeroU16;
580
581        use midenc_hir_type::{AddressSpace, ArrayType, PointerType, StructType, TypeRepr};
582
583        let ty = match source.read_u8()? {
584            0 => Type::Unknown,
585            1 => Type::Never,
586            2 => Type::I1,
587            3 => Type::I8,
588            4 => Type::U8,
589            5 => Type::I16,
590            6 => Type::U16,
591            7 => Type::I32,
592            8 => Type::U32,
593            9 => Type::I64,
594            10 => Type::U64,
595            11 => Type::I128,
596            12 => Type::U128,
597            13 => Type::U256,
598            14 => Type::F64,
599            15 => Type::Felt,
600            16 => {
601                let addrspace = match source.read_u8()? {
602                    0 => AddressSpace::Byte,
603                    1 => AddressSpace::Element,
604                    invalid => {
605                        return Err(DeserializationError::InvalidValue(format!(
606                            "invalid AddressSpace tag: {invalid}"
607                        )));
608                    },
609                };
610                let pointee = TypeDeserializer::read_from(source)?.0;
611                Type::Ptr(Arc::new(PointerType { addrspace, pointee }))
612            },
613            17 => {
614                let repr = match source.read_u8()? {
615                    0 => TypeRepr::Default,
616                    1 => {
617                        let align = NonZeroU16::new(source.read_u16()?).ok_or_else(|| {
618                            DeserializationError::InvalidValue(
619                                "invalid type repr: alignment must be a non-zero value".to_string(),
620                            )
621                        })?;
622                        TypeRepr::Align(align)
623                    },
624                    2 => {
625                        let align = NonZeroU16::new(source.read_u16()?).ok_or_else(|| {
626                            DeserializationError::InvalidValue(
627                                "invalid type repr: packed alignment must be a non-zero value"
628                                    .to_string(),
629                            )
630                        })?;
631                        TypeRepr::Packed(align)
632                    },
633                    3 => TypeRepr::Transparent,
634                    invalid => {
635                        return Err(DeserializationError::InvalidValue(format!(
636                            "invalid TypeRepr tag: {invalid}"
637                        )));
638                    },
639                };
640                let num_fields = source.read_u8()?;
641                let mut fields = SmallVec::<[Type; 4]>::with_capacity(num_fields as usize);
642                for _ in 0..num_fields {
643                    let ty = TypeDeserializer::read_from(source)?.0;
644                    fields.push(ty);
645                }
646                Type::Struct(Arc::new(StructType::new_with_repr(repr, fields)))
647            },
648            18 => {
649                let arity = source.read_usize()?;
650                let ty = TypeDeserializer::read_from(source)?.0;
651                Type::Array(Arc::new(ArrayType { ty, len: arity }))
652            },
653            19 => {
654                let ty = TypeDeserializer::read_from(source)?.0;
655                Type::List(Arc::new(ty))
656            },
657            20 => Type::Function(Arc::new(FunctionTypeDeserializer::read_from(source)?.0)),
658            invalid => {
659                return Err(DeserializationError::InvalidValue(format!(
660                    "invalid Type tag: {invalid}"
661                )));
662            },
663        };
664        Ok(Self(ty))
665    }
666}