Skip to main content

sim_kernel/library/
loaders.rs

1use std::cmp::Ordering;
2use std::sync::Arc;
3
4use crate::{
5    Datum,
6    capability::{CapabilityName, CapabilitySet},
7    env::Cx,
8    error::{Error, Result},
9    factory::Factory,
10    id::{CaseId, ClassId, CodecId, FunctionId, MacroId, NumberDomainId, ShapeId, Symbol},
11    library::{
12        Dependency, LibBootReceipt, LibManifest, LibSourceSpec, Registry, RegistryBootState,
13    },
14    value::Value,
15};
16
17use super::transaction::Linker;
18
19/// The context a [`Lib`] sees while loading.
20///
21/// It exposes the object [`Factory`], read access to the [`Registry`], fresh
22/// stable-id allocation, capability checks, and symbol resolution against
23/// already-loaded exports. The kernel supplies this surface; the library uses
24/// it to wire its behavior in.
25pub struct LoadCx {
26    capabilities: CapabilitySet,
27    factory: Arc<dyn Factory>,
28    registry: Registry,
29}
30
31impl LoadCx {
32    pub(crate) fn new(
33        capabilities: CapabilitySet,
34        factory: Arc<dyn Factory>,
35        registry: Registry,
36    ) -> Self {
37        Self {
38            capabilities,
39            factory,
40            registry,
41        }
42    }
43
44    /// The object factory available during load.
45    pub fn factory(&self) -> &dyn Factory {
46        self.factory.as_ref()
47    }
48
49    /// Read access to the registry as loading proceeds.
50    pub fn registry(&self) -> &Registry {
51        &self.registry
52    }
53
54    /// Reserves a fresh stable class id.
55    pub fn fresh_class_id(&mut self) -> ClassId {
56        self.registry.fresh_class_id()
57    }
58
59    /// Reserves a fresh stable class id, reporting catalog sequence failures.
60    pub fn try_fresh_class_id(&mut self) -> Result<ClassId> {
61        self.registry.try_fresh_class_id()
62    }
63
64    /// Reserves a fresh stable function id.
65    pub fn fresh_function_id(&mut self) -> FunctionId {
66        self.registry.fresh_function_id()
67    }
68
69    /// Reserves a fresh stable function id, reporting catalog sequence failures.
70    pub fn try_fresh_function_id(&mut self) -> Result<FunctionId> {
71        self.registry.try_fresh_function_id()
72    }
73
74    /// Reserves a fresh stable macro id.
75    pub fn fresh_macro_id(&mut self) -> MacroId {
76        self.registry.fresh_macro_id()
77    }
78
79    /// Reserves a fresh stable macro id, reporting catalog sequence failures.
80    pub fn try_fresh_macro_id(&mut self) -> Result<MacroId> {
81        self.registry.try_fresh_macro_id()
82    }
83
84    /// Reserves a fresh stable case id.
85    pub fn fresh_case_id(&mut self) -> CaseId {
86        self.registry.fresh_case_id()
87    }
88
89    /// Reserves a fresh stable case id, reporting catalog sequence failures.
90    pub fn try_fresh_case_id(&mut self) -> Result<CaseId> {
91        self.registry.try_fresh_case_id()
92    }
93
94    /// Reserves a fresh stable shape id.
95    pub fn fresh_shape_id(&mut self) -> ShapeId {
96        self.registry.fresh_shape_id()
97    }
98
99    /// Reserves a fresh stable shape id, reporting catalog sequence failures.
100    pub fn try_fresh_shape_id(&mut self) -> Result<ShapeId> {
101        self.registry.try_fresh_shape_id()
102    }
103
104    /// Reserves a fresh stable codec id.
105    pub fn fresh_codec_id(&mut self) -> CodecId {
106        self.registry.fresh_codec_id()
107    }
108
109    /// Reserves a fresh stable codec id, reporting catalog sequence failures.
110    pub fn try_fresh_codec_id(&mut self) -> Result<CodecId> {
111        self.registry.try_fresh_codec_id()
112    }
113
114    /// Reserves a fresh stable number-domain id.
115    pub fn fresh_number_domain_id(&mut self) -> NumberDomainId {
116        self.registry.fresh_number_domain_id()
117    }
118
119    /// Reserves a fresh stable number-domain id, reporting catalog sequence
120    /// failures.
121    pub fn try_fresh_number_domain_id(&mut self) -> Result<NumberDomainId> {
122        self.registry.try_fresh_number_domain_id()
123    }
124
125    /// Checks that the given capability is granted, returning
126    /// [`Error::CapabilityDenied`](crate::error::Error::CapabilityDenied)
127    /// otherwise.
128    pub fn require(&self, capability: &CapabilityName) -> Result<()> {
129        if self.capabilities.contains(capability) {
130            Ok(())
131        } else {
132            Err(Error::CapabilityDenied {
133                capability: capability.clone(),
134            })
135        }
136    }
137
138    /// Resolves an already-registered class value by symbol.
139    pub fn resolve_class(&self, symbol: &Symbol) -> Result<Value> {
140        self.registry
141            .class_by_symbol(symbol)
142            .cloned()
143            .ok_or_else(|| Error::UnknownClass {
144                class: symbol.clone(),
145            })
146    }
147
148    /// Resolves an already-registered function value by symbol.
149    pub fn resolve_function(&self, symbol: &Symbol) -> Result<Value> {
150        self.registry
151            .function_by_symbol(symbol)
152            .cloned()
153            .ok_or_else(|| Error::UnknownFunction {
154                function: symbol.clone(),
155            })
156    }
157
158    /// Resolves an already-registered macro value by symbol.
159    pub fn resolve_macro(&self, symbol: &Symbol) -> Result<Value> {
160        self.registry
161            .macro_by_symbol(symbol)
162            .cloned()
163            .ok_or_else(|| Error::UnknownSymbol {
164                symbol: symbol.clone(),
165            })
166    }
167
168    /// Resolves an already-registered shape value by symbol.
169    pub fn resolve_shape(&self, symbol: &Symbol) -> Result<Value> {
170        self.registry
171            .shape_by_symbol(symbol)
172            .cloned()
173            .ok_or_else(|| Error::UnknownSymbol {
174                symbol: symbol.clone(),
175            })
176    }
177
178    /// Resolves an already-registered codec value by symbol.
179    pub fn resolve_codec(&self, symbol: &Symbol) -> Result<Value> {
180        self.registry
181            .codec_by_symbol(symbol)
182            .cloned()
183            .ok_or_else(|| Error::UnknownSymbol {
184                symbol: symbol.clone(),
185            })
186    }
187
188    /// Resolves an already-registered number-domain value by symbol.
189    pub fn resolve_number_domain(&self, symbol: &Symbol) -> Result<Value> {
190        self.registry
191            .number_domain_by_symbol(symbol)
192            .cloned()
193            .ok_or_else(|| Error::UnknownSymbol {
194                symbol: symbol.clone(),
195            })
196    }
197
198    /// Resolves an already-registered plain value by symbol.
199    pub fn resolve_value(&self, symbol: &Symbol) -> Result<Value> {
200        self.registry
201            .value_by_symbol(symbol)
202            .cloned()
203            .ok_or_else(|| Error::UnknownSymbol {
204                symbol: symbol.clone(),
205            })
206    }
207}
208
209fn trim_trailing_numeric_zero_components<'a>(components: &'a [&'a str]) -> &'a [&'a str] {
210    let mut end = components.len();
211    while end > 0 && components[end - 1].parse::<u64>() == Ok(0) {
212        end -= 1;
213    }
214    &components[..end]
215}
216
217pub(crate) fn compare_version_text(left: &str, right: &str) -> Ordering {
218    let left_components = left.split('.').collect::<Vec<_>>();
219    let right_components = right.split('.').collect::<Vec<_>>();
220    let left = trim_trailing_numeric_zero_components(&left_components);
221    let right = trim_trailing_numeric_zero_components(&right_components);
222    let len = left.len().max(right.len());
223    for index in 0..len {
224        let left_component = left.get(index).copied().unwrap_or("0");
225        let right_component = right.get(index).copied().unwrap_or("0");
226        let ordering = match (
227            left_component.parse::<u64>(),
228            right_component.parse::<u64>(),
229        ) {
230            (Ok(left_number), Ok(right_number)) => left_number.cmp(&right_number),
231            _ => left_component.cmp(right_component),
232        };
233        if ordering != Ordering::Equal {
234            return ordering;
235        }
236    }
237    Ordering::Equal
238}
239
240pub(crate) fn dependency_satisfied(loaded: &LibManifest, dependency: &Dependency) -> bool {
241    match &dependency.minimum_version {
242        Some(minimum) => compare_version_text(&loaded.version.0, &minimum.0) != Ordering::Less,
243        None => true,
244    }
245}
246
247/// The contract every loadable library implements.
248///
249/// A library presents its [`LibManifest`] and wires its behavior into the
250/// registry through a [`Linker`] during [`load`](Lib::load). The kernel defines
251/// this contract; libraries provide the behavior.
252pub trait Lib {
253    /// Returns the library's manifest.
254    fn manifest(&self) -> LibManifest;
255    /// Registers the library's exports against the registry via `linker`.
256    fn load(&self, cx: &mut LoadCx, linker: &mut Linker) -> Result<()>;
257
258    /// Tears down external resources that cannot be represented in the
259    /// registry's recorded load delta.
260    ///
261    /// Normal export removal is driven by [`Registry::unload`], which retracts
262    /// the committed registry records for a loaded [`LibId`](crate::LibId).
263    /// Libraries should only override this hook when they acquire external
264    /// resources outside the registry receipt; the default means there is no
265    /// custom external teardown.
266    fn unload(&self, _cx: &mut Cx, _linker: &mut Linker) -> Result<()> {
267        Ok(())
268    }
269}
270
271/// Where a library is loaded from.
272pub enum LibSource {
273    /// A catalog-resolved library symbol.
274    Symbol(Symbol),
275    /// A loader-defined source carried as data.
276    Open {
277        /// Loader-defined source kind.
278        kind: Symbol,
279        /// Opaque payload interpreted by the loader that claims `kind`.
280        payload: Datum,
281    },
282    /// A host-constructed library object.
283    Host(Box<dyn Lib>),
284}
285
286impl LibSource {
287    /// Builds a loader-defined source carried as opaque data.
288    pub fn open(kind: Symbol, payload: Datum) -> Self {
289        Self::Open { kind, payload }
290    }
291}
292
293/// A catalog-registered source for a library symbol.
294///
295/// Unlike [`LibSource`], this excludes the in-process `Symbol`/`Host` variants,
296/// so it can be stored and resolved by symbol; it converts into a [`LibSource`]
297/// on use.
298#[derive(Clone, Debug, PartialEq, Eq)]
299pub enum CatalogSource {
300    /// A loader-defined source carried as data.
301    Open {
302        /// Loader-defined source kind.
303        kind: Symbol,
304        /// Opaque payload interpreted by the loader that claims `kind`.
305        payload: Datum,
306    },
307}
308
309impl CatalogSource {
310    /// Builds a loader-defined catalog source carried as opaque data.
311    pub fn open(kind: Symbol, payload: Datum) -> Self {
312        Self::Open { kind, payload }
313    }
314}
315
316impl From<CatalogSource> for LibSource {
317    fn from(source: CatalogSource) -> Self {
318        match source {
319            CatalogSource::Open { kind, payload } => Self::Open { kind, payload },
320        }
321    }
322}
323
324/// A loader that can turn a [`LibSource`] into a [`Lib`].
325///
326/// Loaders are themselves library behavior; the kernel only defines the
327/// contract for plugging them in.
328pub trait LibLoader: Send + Sync {
329    /// Reports whether this loader accepts the given source.
330    fn can_load(&self, source: &LibSource) -> bool;
331    /// Loads the source into a library object.
332    fn load(&self, cx: &mut Cx, source: LibSource) -> Result<Box<dyn Lib>>;
333
334    /// Inspects a source's manifest without fully loading it; the default
335    /// returns `None` (not supported).
336    fn inspect_manifest(&self, _cx: &mut Cx, _source: &LibSource) -> Result<Option<LibManifest>> {
337        Ok(None)
338    }
339}
340
341/// A registry of [`LibLoader`]s plus catalog-resolvable library sources.
342#[derive(Default)]
343pub struct LoaderRegistry {
344    loaders: Vec<Box<dyn LibLoader>>,
345    sources: std::collections::BTreeMap<Symbol, CatalogSource>,
346}
347
348impl LoaderRegistry {
349    /// Creates an empty loader registry.
350    pub fn new() -> Self {
351        Self::default()
352    }
353
354    /// Adds a loader, builder-style.
355    pub fn with_loader(mut self, loader: impl LibLoader + 'static) -> Self {
356        self.loaders.push(Box::new(loader));
357        self
358    }
359
360    /// Adds a loader.
361    pub fn add_loader(&mut self, loader: impl LibLoader + 'static) {
362        self.loaders.push(Box::new(loader));
363    }
364
365    /// Registers a catalog source for a library symbol, builder-style.
366    pub fn with_source(mut self, symbol: Symbol, source: CatalogSource) -> Self {
367        self.sources.insert(symbol, source);
368        self
369    }
370
371    /// Registers a catalog source for a library symbol.
372    pub fn add_source(&mut self, symbol: Symbol, source: CatalogSource) {
373        self.sources.insert(symbol, source);
374    }
375
376    /// Loads a library, resolving catalog symbols and dispatching to the first
377    /// accepting loader.
378    pub fn load_lib(&self, cx: &mut Cx, source: LibSource) -> Result<Box<dyn Lib>> {
379        if let LibSource::Symbol(symbol) = &source
380            && let Some(resolved) = self.sources.get(symbol).cloned()
381        {
382            return self.load_lib(cx, resolved.into());
383        }
384        for loader in &self.loaders {
385            if loader.can_load(&source) {
386                return loader.load(cx, source);
387            }
388        }
389        match source {
390            LibSource::Symbol(symbol) => Err(Error::HostError(format!(
391                "no loader accepted lib source symbol {}",
392                symbol
393            ))),
394            LibSource::Open { kind, .. } => Err(Error::HostError(format!(
395                "no loader accepted lib source kind {}",
396                kind
397            ))),
398            _ => Err(Error::HostError("no loader accepted lib source".to_owned())),
399        }
400    }
401
402    /// Loads a library and registers it into the registry, returning its id.
403    pub fn load_and_register(&self, cx: &mut Cx, source: LibSource) -> Result<crate::LibId> {
404        let lib = self.load_lib(cx, source)?;
405        cx.load_lib(lib.as_ref())
406    }
407
408    /// Resolves a data-only source spec through this registry's catalog.
409    pub fn resolve_source_spec(&self, source: &LibSourceSpec) -> LibSourceSpec {
410        match source {
411            LibSourceSpec::Symbol(symbol) => self
412                .sources
413                .get(symbol)
414                .cloned()
415                .map(LibSourceSpec::from)
416                .unwrap_or_else(|| source.clone()),
417            _ => source.clone(),
418        }
419    }
420
421    /// Loads a data-only source and returns the boot receipt for the committed
422    /// library.
423    pub fn load_and_register_with_receipt(
424        &self,
425        cx: &mut Cx,
426        source: LibSourceSpec,
427    ) -> Result<LibBootReceipt> {
428        let resolved = self.resolve_source_spec(&source);
429        self.load_and_register_from_specs(cx, source, resolved)
430    }
431
432    /// Replays load-order boot receipts, verifying that each replayed library
433    /// produces the same receipt data.
434    pub fn replay_boot_receipts(
435        &self,
436        cx: &mut Cx,
437        receipts: &[LibBootReceipt],
438    ) -> Result<Vec<LibBootReceipt>> {
439        receipts
440            .iter()
441            .map(|expected| {
442                let actual = self.load_and_register_from_specs(
443                    cx,
444                    expected.requested_source.clone(),
445                    expected.resolved_source.clone(),
446                )?;
447                if &actual == expected {
448                    Ok(actual)
449                } else {
450                    Err(Error::Lib(format!(
451                        "boot receipt replay mismatch for {}",
452                        expected.manifest.id
453                    )))
454                }
455            })
456            .collect()
457    }
458
459    /// Replays a registry boot state and returns the receipts produced by the
460    /// replay.
461    pub fn replay_boot_state(
462        &self,
463        cx: &mut Cx,
464        state: &RegistryBootState,
465    ) -> Result<Vec<LibBootReceipt>> {
466        self.replay_boot_receipts(cx, &state.receipts)
467    }
468
469    /// Inspects a source's manifest without loading it, resolving catalog
470    /// symbols first.
471    pub fn inspect_manifest(&self, cx: &mut Cx, source: LibSource) -> Result<LibManifest> {
472        if let LibSource::Symbol(symbol) = &source
473            && let Some(resolved) = self.sources.get(symbol).cloned()
474        {
475            return self.inspect_manifest(cx, resolved.into());
476        }
477        for loader in &self.loaders {
478            if loader.can_load(&source) {
479                if let Some(manifest) = loader.inspect_manifest(cx, &source)? {
480                    return Ok(manifest);
481                }
482                return Err(Error::HostError(
483                    "loader does not support manifest inspection before load".to_owned(),
484                ));
485            }
486        }
487        match source {
488            LibSource::Symbol(symbol) => Err(Error::HostError(format!(
489                "no loader accepted lib source symbol {}",
490                symbol
491            ))),
492            LibSource::Open { kind, .. } => Err(Error::HostError(format!(
493                "no loader accepted lib source kind {} for inspection",
494                kind
495            ))),
496            _ => Err(Error::HostError(
497                "no loader accepted lib source for inspection".to_owned(),
498            )),
499        }
500    }
501
502    fn load_and_register_from_specs(
503        &self,
504        cx: &mut Cx,
505        requested: LibSourceSpec,
506        resolved: LibSourceSpec,
507    ) -> Result<LibBootReceipt> {
508        let lib = self.load_lib(cx, resolved.clone().into())?;
509        let lib_id = cx.load_lib(lib.as_ref())?;
510        cx.registry()
511            .boot_receipt(lib_id, requested, resolved)
512            .ok_or_else(|| Error::Lib(format!("loaded lib id {lib_id:?} is not registered")))
513    }
514}