harn_vm/prepared_module/
generation.rs1use std::collections::BTreeMap;
2use std::num::NonZeroUsize;
3use std::path::{Path, PathBuf};
4use std::sync::Arc;
5
6use super::{PreparedModuleCache, PreparedModuleCacheStats, MAX_REMEMBERED_INTERFACES};
7use crate::module_artifact::ModuleProvenance;
8use crate::{ModulePhaseStats, VmError};
9
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
12#[non_exhaustive]
13pub struct PreparedModuleGenerationStats {
14 pub phases: ModulePhaseStats,
15 pub cache: PreparedModuleCacheStats,
16 pub source_modules: u64,
17 pub source_bytes: u64,
18 pub source_digest_blake3: [u8; 32],
19}
20
21impl PreparedModuleCache {
22 pub fn for_immutable_generation() -> Self {
27 Self::with_capacity(
28 NonZeroUsize::new(MAX_REMEMBERED_INTERFACES)
29 .expect("immutable generation capacity is non-zero"),
30 )
31 }
32
33 pub(crate) fn source_snapshot(&self) -> Arc<BTreeMap<PathBuf, Arc<str>>> {
34 Arc::new(
35 self.sources
36 .lock()
37 .expect("prepared-module source lock poisoned")
38 .clone(),
39 )
40 }
41
42 pub fn prepare_module_generation(
45 &self,
46 roots: &[PathBuf],
47 ) -> Result<PreparedModuleGenerationStats, VmError> {
48 let phases = self.prepare_graph_with_provenance(roots, ModuleProvenance::User, true)?;
49 Ok(self.generation_stats(phases, roots))
50 }
51
52 pub fn prepare_trusted_host_dispatch_generation(
54 &self,
55 roots: &[PathBuf],
56 ) -> Result<PreparedModuleGenerationStats, VmError> {
57 let phases =
58 self.prepare_graph_with_provenance(roots, ModuleProvenance::TrustedHostDispatch, true)?;
59 Ok(self.generation_stats(phases, roots))
60 }
61
62 fn generation_stats(
63 &self,
64 phases: ModulePhaseStats,
65 roots: &[PathBuf],
66 ) -> PreparedModuleGenerationStats {
67 let sources = self
68 .sources
69 .lock()
70 .expect("prepared-module source lock poisoned");
71 let root_dir = roots
72 .first()
73 .and_then(|root| root.parent())
74 .map(harn_modules::canonical_path)
75 .unwrap_or_default();
76 let logical_sources = sources
77 .iter()
78 .map(|(path, source)| {
79 let canonical = harn_modules::canonical_path(path);
80 (logical_generation_path(&root_dir, &canonical), source)
81 })
82 .collect::<BTreeMap<_, _>>();
83 PreparedModuleGenerationStats {
84 phases,
85 cache: self.stats(),
86 source_modules: logical_sources.len() as u64,
87 source_bytes: logical_sources
88 .values()
89 .map(|source| source.len() as u64)
90 .sum(),
91 source_digest_blake3: {
92 let mut hasher = blake3::Hasher::new();
93 hasher.update(b"harn-prepared-module-generation-v1\0");
94 for (path, source) in logical_sources {
95 hasher.update(&(path.len() as u64).to_le_bytes());
96 hasher.update(path.as_bytes());
97 hasher.update(&(source.len() as u64).to_le_bytes());
98 hasher.update(source.as_bytes());
99 }
100 *hasher.finalize().as_bytes()
101 },
102 }
103 }
104}
105
106fn logical_generation_path(root: &Path, path: &Path) -> String {
107 if let Ok(relative) = path.strip_prefix(root) {
108 return relative
109 .components()
110 .map(|part| part.as_os_str().to_string_lossy())
111 .collect::<Vec<_>>()
112 .join("/");
113 }
114
115 let root_parts = root.components().collect::<Vec<_>>();
116 let path_parts = path.components().collect::<Vec<_>>();
117 let shared = root_parts
118 .iter()
119 .zip(&path_parts)
120 .take_while(|(left, right)| left == right)
121 .count();
122 if shared == 0 {
123 return "<external>".to_string();
124 }
125 std::iter::repeat_n("..".to_string(), root_parts.len() - shared)
126 .chain(
127 path_parts[shared..]
128 .iter()
129 .map(|part| part.as_os_str().to_string_lossy().into_owned()),
130 )
131 .collect::<Vec<_>>()
132 .join("/")
133}