1use std::{
4 collections::BTreeMap,
5 sync::{Arc, Condvar, Mutex, MutexGuard},
6 thread::ThreadId,
7};
8
9use sim_kernel::{CapabilityName, CapabilitySet, Cx, Dir, Error, Expr, ReadPolicy, Result, Symbol};
10use sim_lib_binding::BindingCell;
11use sim_lib_core::{ReadEvalBroker, ReadEvalRequest, ReadEvalSource, RequestOrigin};
12use sim_shape::AnyShape;
13
14pub fn module_load_capability() -> CapabilityName {
16 CapabilityName::new("namespace.module.load")
17}
18
19#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub struct ModuleIdentity {
22 root: Symbol,
23 path: String,
24}
25
26impl ModuleIdentity {
27 pub fn root(&self) -> &Symbol {
29 &self.root
30 }
31 pub fn path(&self) -> &str {
33 &self.path
34 }
35}
36
37pub struct ModuleRequest {
39 pub root_id: Symbol,
41 pub root: Arc<dyn Dir>,
43 pub importer: Option<ModuleIdentity>,
45 pub specifier: String,
47 pub codec: Symbol,
49 pub read_policy: ReadPolicy,
51 pub requires: Vec<CapabilityName>,
53 pub allow: CapabilitySet,
55}
56
57#[derive(Clone, Debug)]
59pub struct ModuleInstance {
60 identity: ModuleIdentity,
61 generation: u64,
62 default_export: BindingCell,
63}
64
65impl ModuleInstance {
66 pub fn identity(&self) -> &ModuleIdentity {
68 &self.identity
69 }
70 pub fn generation(&self) -> u64 {
72 self.generation
73 }
74 pub fn default_export(&self) -> &BindingCell {
76 &self.default_export
77 }
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
82pub enum ModuleResolutionOutcome {
83 Linked,
85 CacheHit,
87 Failed,
89 Cycle,
91}
92
93#[derive(Clone, Debug, PartialEq, Eq)]
95pub struct ModuleResolutionReceipt {
96 pub identity: ModuleIdentity,
98 pub generation: u64,
100 pub outcome: ModuleResolutionOutcome,
102 pub detail: Option<String>,
104}
105
106enum CacheState {
107 Initializing {
108 owner: ThreadId,
109 generation: u64,
110 },
111 Linked(ModuleInstance),
112 Failed {
113 generation: u64,
114 message: String,
115 binding: BindingCell,
116 },
117}
118
119#[derive(Default)]
120struct LoaderState {
121 cache: BTreeMap<ModuleIdentity, CacheState>,
122 receipts: Vec<ModuleResolutionReceipt>,
123}
124
125#[derive(Default)]
127pub struct ModuleLoader {
128 state: Mutex<LoaderState>,
129 changed: Condvar,
130 broker: ReadEvalBroker,
131}
132
133impl ModuleLoader {
134 pub fn new() -> Self {
136 Self::default()
137 }
138
139 pub fn load(&self, cx: &mut Cx, request: ModuleRequest) -> Result<ModuleInstance> {
141 cx.require(&module_load_capability())?;
142 let identity = canonical_identity(&request)?;
143 let owner = std::thread::current().id();
144 let (generation, binding) = loop {
145 let mut state = self.lock_state()?;
146 match state.cache.get(&identity) {
147 Some(CacheState::Linked(instance)) => {
148 let instance = instance.clone();
149 push_receipt(
150 &mut state,
151 &identity,
152 instance.generation,
153 ModuleResolutionOutcome::CacheHit,
154 None,
155 );
156 return Ok(instance);
157 }
158 Some(CacheState::Failed {
159 generation,
160 message,
161 ..
162 }) => {
163 let generation = *generation;
164 let message = message.clone();
165 push_receipt(
166 &mut state,
167 &identity,
168 generation,
169 ModuleResolutionOutcome::Failed,
170 Some(message.clone()),
171 );
172 return Err(Error::Eval(message));
173 }
174 Some(CacheState::Initializing {
175 owner: active,
176 generation,
177 ..
178 }) if *active == owner => {
179 let generation = *generation;
180 let message = format!("module cycle at {}:{}", identity.root, identity.path);
181 push_receipt(
182 &mut state,
183 &identity,
184 generation,
185 ModuleResolutionOutcome::Cycle,
186 Some(message.clone()),
187 );
188 return Err(Error::Eval(message));
189 }
190 Some(CacheState::Initializing { .. }) => {
191 drop(
192 self.changed
193 .wait(state)
194 .map_err(|_| Error::PoisonedLock("module loader"))?,
195 );
196 continue;
197 }
198 None => {
199 let binding = BindingCell::uninitialized(Symbol::new(identity.path.clone()));
200 state.cache.insert(
201 identity.clone(),
202 CacheState::Initializing {
203 owner,
204 generation: 1,
205 },
206 );
207 break (1, binding);
208 }
209 }
210 };
211 self.finish_load(cx, request, identity, generation, binding)
212 }
213
214 pub fn reload(&self, cx: &mut Cx, request: ModuleRequest) -> Result<ModuleInstance> {
216 cx.require(&module_load_capability())?;
217 let identity = canonical_identity(&request)?;
218 let owner = std::thread::current().id();
219 let (generation, binding) = {
220 let mut state = self.lock_state()?;
221 let (generation, binding) = match state.cache.remove(&identity) {
222 Some(CacheState::Linked(instance)) => {
223 (instance.generation + 1, instance.default_export)
224 }
225 Some(CacheState::Failed {
226 generation,
227 binding,
228 ..
229 }) => (generation + 1, binding),
230 Some(initializing @ CacheState::Initializing { .. }) => {
231 state.cache.insert(identity.clone(), initializing);
232 return Err(Error::Eval(format!(
233 "cannot replace initializing module {}:{}",
234 identity.root, identity.path
235 )));
236 }
237 None => (
238 1,
239 BindingCell::uninitialized(Symbol::new(identity.path.clone())),
240 ),
241 };
242 state.cache.insert(
243 identity.clone(),
244 CacheState::Initializing { owner, generation },
245 );
246 (generation, binding)
247 };
248 self.finish_load(cx, request, identity, generation, binding)
249 }
250
251 pub fn receipts(&self) -> Result<Vec<ModuleResolutionReceipt>> {
253 Ok(self.lock_state()?.receipts.clone())
254 }
255
256 fn finish_load(
257 &self,
258 cx: &mut Cx,
259 request: ModuleRequest,
260 identity: ModuleIdentity,
261 generation: u64,
262 binding: BindingCell,
263 ) -> Result<ModuleInstance> {
264 let result = (|| {
265 let source = read_source(cx, request.root.as_ref(), &identity.path)?;
266 self.broker.admit(
267 cx,
268 ReadEvalRequest {
269 origin: RequestOrigin::with_detail(
270 Symbol::qualified("namespace", "module"),
271 Expr::String(format!("{}:{}", identity.root, identity.path)),
272 ),
273 codec: request.codec,
274 source,
275 read_policy: request.read_policy,
276 requires: request.requires,
277 allow: request.allow,
278 expected_shape: Arc::new(AnyShape),
279 },
280 )
281 })();
282 let mut state = self.lock_state()?;
283 match result {
284 Ok(value) => {
285 binding.set(value)?;
286 let instance = ModuleInstance {
287 identity: identity.clone(),
288 generation,
289 default_export: binding,
290 };
291 state
292 .cache
293 .insert(identity.clone(), CacheState::Linked(instance.clone()));
294 push_receipt(
295 &mut state,
296 &identity,
297 generation,
298 ModuleResolutionOutcome::Linked,
299 None,
300 );
301 self.changed.notify_all();
302 Ok(instance)
303 }
304 Err(error) => {
305 let message = error.to_string();
306 state.cache.insert(
307 identity.clone(),
308 CacheState::Failed {
309 generation,
310 message: message.clone(),
311 binding,
312 },
313 );
314 push_receipt(
315 &mut state,
316 &identity,
317 generation,
318 ModuleResolutionOutcome::Failed,
319 Some(message.clone()),
320 );
321 self.changed.notify_all();
322 Err(Error::Eval(message))
323 }
324 }
325 }
326
327 fn lock_state(&self) -> Result<MutexGuard<'_, LoaderState>> {
328 self.state
329 .lock()
330 .map_err(|_| Error::PoisonedLock("module loader"))
331 }
332}
333
334fn canonical_identity(request: &ModuleRequest) -> Result<ModuleIdentity> {
335 let absolute = request.specifier.starts_with('/');
336 if absolute {
337 return Err(Error::Eval(
338 "module specifier must be root-relative, not absolute".to_owned(),
339 ));
340 }
341 let mut parts = if request.specifier.starts_with('.') {
342 let importer = request
343 .importer
344 .as_ref()
345 .ok_or_else(|| Error::Eval("relative module request has no importer".to_owned()))?;
346 if importer.root != request.root_id {
347 return Err(Error::Eval(
348 "relative module request crosses supplied roots".to_owned(),
349 ));
350 }
351 let mut base: Vec<&str> = importer.path.split('/').collect();
352 base.pop();
353 base
354 } else {
355 Vec::new()
356 };
357 for part in request.specifier.split('/') {
358 match part {
359 "" | "." => {}
360 ".." => {
361 if parts.pop().is_none() {
362 return Err(Error::Eval(
363 "module request escapes supplied root".to_owned(),
364 ));
365 }
366 }
367 component if component.contains('\\') => {
368 return Err(Error::Eval(
369 "module path contains a non-canonical separator".to_owned(),
370 ));
371 }
372 component => parts.push(component),
373 }
374 }
375 if parts.is_empty() {
376 return Err(Error::Eval("module path is empty".to_owned()));
377 }
378 Ok(ModuleIdentity {
379 root: request.root_id.clone(),
380 path: parts.join("/"),
381 })
382}
383
384fn read_source(cx: &mut Cx, root: &dyn Dir, path: &str) -> Result<ReadEvalSource> {
385 let components = path.split('/').collect::<Vec<_>>();
386 read_source_at(cx, root, &components, path)
387}
388
389fn read_source_at(
390 cx: &mut Cx,
391 dir: &dyn Dir,
392 components: &[&str],
393 path: &str,
394) -> Result<ReadEvalSource> {
395 let (component, rest) = components
396 .split_first()
397 .ok_or_else(|| Error::Eval("module path is empty".to_owned()))?;
398 let key = Symbol::new(*component);
399 if rest.is_empty() {
400 if !dir.has(cx, key.clone())? {
401 return Err(Error::Eval(format!("module source not found: {path}")));
402 }
403 let value = dir.get(cx, key)?;
404 return match value.object().as_expr(cx)? {
405 Expr::String(text) => Ok(ReadEvalSource::Text(text)),
406 Expr::Bytes(bytes) => Ok(ReadEvalSource::Bytes(bytes)),
407 _ => Err(Error::Eval(format!(
408 "module source is not text or bytes: {path}"
409 ))),
410 };
411 }
412 let value = dir
413 .opendir(cx, key)?
414 .ok_or_else(|| Error::Eval(format!("module directory not found: {path}")))?;
415 let child = value
416 .object()
417 .as_dir()
418 .ok_or_else(|| Error::Eval(format!("module path component is not a Dir: {component}")))?;
419 read_source_at(cx, child, rest, path)
420}
421
422fn push_receipt(
423 state: &mut LoaderState,
424 identity: &ModuleIdentity,
425 generation: u64,
426 outcome: ModuleResolutionOutcome,
427 detail: Option<String>,
428) {
429 state.receipts.push(ModuleResolutionReceipt {
430 identity: identity.clone(),
431 generation,
432 outcome,
433 detail,
434 });
435}
436
437#[cfg(test)]
438mod tests;