Skip to main content

sim_kernel/library/
loaders.rs

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