1use alloc::{
2 collections::{BTreeMap, BTreeSet},
3 string::String,
4 sync::Arc,
5 vec::Vec,
6};
7
8use vm_core::{
9 crypto::hash::RpoDigest,
10 debuginfo::Span,
11 mast::{MastForest, MastNodeId},
12 utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
13 Kernel,
14};
15
16use crate::ast::{Ident, ProcedureName, QualifiedProcedureName};
17
18mod error;
19mod module;
20mod namespace;
21mod path;
22mod version;
23
24pub use module::{ModuleInfo, ProcedureInfo};
25
26pub use self::{
27 error::LibraryError,
28 namespace::{LibraryNamespace, LibraryNamespaceError},
29 path::{LibraryPath, LibraryPathComponent, PathError},
30 version::{Version, VersionError},
31};
32
33#[cfg(test)]
34mod tests;
35
36#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct Library {
45 digest: RpoDigest,
48 exports: BTreeMap<QualifiedProcedureName, MastNodeId>,
58 mast_forest: Arc<MastForest>,
60}
61
62impl AsRef<Library> for Library {
63 #[inline(always)]
64 fn as_ref(&self) -> &Library {
65 self
66 }
67}
68
69impl Library {
72 pub fn new(
79 mast_forest: Arc<MastForest>,
80 exports: BTreeMap<QualifiedProcedureName, MastNodeId>,
81 ) -> Result<Self, LibraryError> {
82 if exports.is_empty() {
83 return Err(LibraryError::NoExport);
84 }
85 for (fqn, &proc_body_id) in exports.iter() {
86 if !mast_forest.is_procedure_root(proc_body_id) {
87 return Err(LibraryError::NoProcedureRootForExport { procedure_path: fqn.clone() });
88 }
89 }
90
91 let digest = compute_content_hash(&exports, &mast_forest);
92
93 Ok(Self { digest, exports, mast_forest })
94 }
95}
96
97impl Library {
100 pub fn digest(&self) -> &RpoDigest {
102 &self.digest
103 }
104
105 pub fn exports(&self) -> impl Iterator<Item = &QualifiedProcedureName> {
107 self.exports.keys()
108 }
109
110 pub fn num_exports(&self) -> usize {
112 self.exports.len()
113 }
114
115 pub fn get_export_node_id(&self, proc_name: &QualifiedProcedureName) -> MastNodeId {
120 *self.exports.get(proc_name).expect("procedure not exported from the library")
121 }
122
123 pub fn is_reexport(&self, proc_name: &QualifiedProcedureName) -> bool {
125 self.exports
126 .get(proc_name)
127 .map(|&node_id| self.mast_forest[node_id].is_external())
128 .unwrap_or(false)
129 }
130
131 pub fn mast_forest(&self) -> &Arc<MastForest> {
133 &self.mast_forest
134 }
135}
136
137impl Library {
139 pub fn module_infos(&self) -> impl Iterator<Item = ModuleInfo> {
141 let mut modules_by_path: BTreeMap<LibraryPath, ModuleInfo> = BTreeMap::new();
142
143 for (proc_name, &proc_root_node_id) in self.exports.iter() {
144 modules_by_path
145 .entry(proc_name.module.clone())
146 .and_modify(|compiled_module| {
147 let proc_digest = self.mast_forest[proc_root_node_id].digest();
148 compiled_module.add_procedure(proc_name.name.clone(), proc_digest);
149 })
150 .or_insert_with(|| {
151 let mut module_info = ModuleInfo::new(proc_name.module.clone());
152
153 let proc_digest = self.mast_forest[proc_root_node_id].digest();
154 module_info.add_procedure(proc_name.name.clone(), proc_digest);
155
156 module_info
157 });
158 }
159
160 modules_by_path.into_values()
161 }
162}
163
164impl Serializable for Library {
165 fn write_into<W: ByteWriter>(&self, target: &mut W) {
166 let Self { digest: _, exports, mast_forest } = self;
167
168 mast_forest.write_into(target);
169
170 target.write_usize(exports.len());
171 for (proc_name, proc_node_id) in exports {
172 proc_name.module.write_into(target);
173 proc_name.name.as_str().write_into(target);
174 target.write_u32(proc_node_id.as_u32());
175 }
176 }
177}
178
179impl Deserializable for Library {
180 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
181 let mast_forest = Arc::new(MastForest::read_from(source)?);
182
183 let num_exports = source.read_usize()?;
184 if num_exports == 0 {
185 return Err(DeserializationError::InvalidValue(String::from("No exported procedures")));
186 };
187 let mut exports = BTreeMap::new();
188 for _ in 0..num_exports {
189 let proc_module = source.read()?;
190 let proc_name: String = source.read()?;
191 let proc_name = ProcedureName::new_unchecked(Ident::new_unchecked(Span::unknown(
192 Arc::from(proc_name),
193 )));
194 let proc_name = QualifiedProcedureName::new(proc_module, proc_name);
195 let proc_node_id = MastNodeId::from_u32_safe(source.read_u32()?, &mast_forest)?;
196
197 exports.insert(proc_name, proc_node_id);
198 }
199
200 let digest = compute_content_hash(&exports, &mast_forest);
201
202 Ok(Self { digest, exports, mast_forest })
203 }
204}
205
206fn compute_content_hash(
207 exports: &BTreeMap<QualifiedProcedureName, MastNodeId>,
208 mast_forest: &MastForest,
209) -> RpoDigest {
210 let digests = BTreeSet::from_iter(exports.values().map(|&id| mast_forest[id].digest()));
211 digests
212 .into_iter()
213 .reduce(|a, b| vm_core::crypto::hash::Rpo256::merge(&[a, b]))
214 .unwrap()
215}
216
217#[cfg(feature = "std")]
218mod use_std_library {
219 use std::{fs, io, path::Path};
220
221 use miette::Report;
222 use vm_core::utils::ReadAdapter;
223
224 use super::*;
225 use crate::Assembler;
226
227 impl Library {
228 pub const LIBRARY_EXTENSION: &'static str = "masl";
230
231 pub fn write_to_file(&self, path: impl AsRef<Path>) -> io::Result<()> {
237 let path = path.as_ref();
238
239 if let Some(dir) = path.parent() {
240 fs::create_dir_all(dir)?;
241 }
242
243 std::panic::catch_unwind(|| {
247 let mut file = fs::File::create(path)?;
248 self.write_into(&mut file);
249 Ok(())
250 })
251 .map_err(|p| {
252 match p.downcast::<io::Error>() {
253 Ok(err) => unsafe { core::ptr::read(&*err) },
255 Err(err) => std::panic::resume_unwind(err),
256 }
257 })?
258 }
259
260 pub fn from_dir(
293 path: impl AsRef<Path>,
294 namespace: LibraryNamespace,
295 assembler: Assembler,
296 ) -> Result<Self, Report> {
297 let path = path.as_ref();
298
299 let modules =
300 crate::parser::read_modules_from_dir(namespace, path, &assembler.source_manager())?;
301 assembler.assemble_library(modules)
302 }
303
304 pub fn deserialize_from_file(path: impl AsRef<Path>) -> Result<Self, DeserializationError> {
305 let path = path.as_ref();
306 let mut file = fs::File::open(path).map_err(|err| {
307 DeserializationError::InvalidValue(format!(
308 "failed to open file at {}: {err}",
309 path.to_string_lossy()
310 ))
311 })?;
312 let mut adapter = ReadAdapter::new(&mut file);
313
314 Self::read_from(&mut adapter)
315 }
316 }
317}
318
319#[derive(Debug, Clone, PartialEq, Eq)]
329pub struct KernelLibrary {
330 kernel: Kernel,
331 kernel_info: ModuleInfo,
332 library: Library,
333}
334
335impl AsRef<Library> for KernelLibrary {
336 #[inline(always)]
337 fn as_ref(&self) -> &Library {
338 &self.library
339 }
340}
341
342impl KernelLibrary {
343 pub fn kernel(&self) -> &Kernel {
345 &self.kernel
346 }
347
348 pub fn mast_forest(&self) -> &Arc<MastForest> {
350 self.library.mast_forest()
351 }
352
353 pub fn into_parts(self) -> (Kernel, ModuleInfo, Arc<MastForest>) {
355 (self.kernel, self.kernel_info, self.library.mast_forest)
356 }
357}
358
359impl TryFrom<Library> for KernelLibrary {
360 type Error = LibraryError;
361
362 fn try_from(library: Library) -> Result<Self, Self::Error> {
363 let kernel_path = LibraryPath::from(LibraryNamespace::Kernel);
364 let mut proc_digests = Vec::with_capacity(library.exports.len());
365
366 let mut kernel_module = ModuleInfo::new(kernel_path.clone());
367
368 for (proc_path, &proc_node_id) in library.exports.iter() {
369 if proc_path.module != kernel_path {
371 return Err(LibraryError::InvalidKernelExport {
372 procedure_path: proc_path.clone(),
373 });
374 }
375
376 let proc_digest = library.mast_forest[proc_node_id].digest();
377 proc_digests.push(proc_digest);
378 kernel_module.add_procedure(proc_path.name.clone(), proc_digest);
379 }
380
381 let kernel = Kernel::new(&proc_digests).map_err(LibraryError::KernelConversion)?;
382
383 Ok(Self {
384 kernel,
385 kernel_info: kernel_module,
386 library,
387 })
388 }
389}
390
391impl Serializable for KernelLibrary {
392 fn write_into<W: ByteWriter>(&self, target: &mut W) {
393 let Self { kernel: _, kernel_info: _, library } = self;
394
395 library.write_into(target);
396 }
397}
398
399impl Deserializable for KernelLibrary {
400 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
401 let library = Library::read_from(source)?;
402
403 Self::try_from(library).map_err(|err| {
404 DeserializationError::InvalidValue(format!(
405 "Failed to deserialize kernel library: {err}"
406 ))
407 })
408 }
409}
410
411#[cfg(feature = "std")]
412mod use_std_kernel {
413 use std::{io, path::Path};
414
415 use super::*;
416 use crate::{diagnostics::Report, Assembler};
417
418 impl KernelLibrary {
419 pub fn write_to_file(&self, path: impl AsRef<Path>) -> io::Result<()> {
421 self.library.write_to_file(path)
422 }
423
424 pub fn from_dir(
438 sys_module_path: impl AsRef<Path>,
439 lib_dir: Option<impl AsRef<Path>>,
440 mut assembler: Assembler,
441 ) -> Result<Self, Report> {
442 if let Some(lib_dir) = lib_dir {
444 let lib_dir = lib_dir.as_ref();
445 let namespace = LibraryNamespace::new("kernel").expect("invalid namespace");
446 assembler.add_modules_from_dir(namespace, lib_dir)?;
447 }
448
449 assembler.assemble_kernel(sys_module_path.as_ref())
450 }
451 }
452}