Skip to main content

sim_citizen/
registry.rs

1//! Registry rows and library installation for registered citizens.
2
3use std::collections::BTreeSet;
4
5use sim_kernel::{
6    AbiVersion, Error, Export, Lib, LibManifest, LibTarget, Linker, LoadCx, Result, Symbol, Version,
7};
8
9use crate::{CitizenRuntime, parse_symbol};
10
11/// Installs a citizen's class into a kernel [`Linker`].
12pub type InstallFn = for<'a> fn(&mut Linker<'a>) -> Result<()>;
13/// Runs a citizen's conformance fixture against a kernel context.
14pub type ConformanceFn = fn(&mut sim_kernel::Cx) -> Result<()>;
15
16/// One inventory-registered citizen's static registry row.
17///
18/// Collected via `inventory` so every linked crate's citizens are discoverable
19/// without a central list. Carries identity metadata plus the install and
20/// conformance hooks driving registration and the strict gate.
21#[derive(Clone, Copy)]
22pub struct CitizenInfo {
23    /// The citizen's `namespace/name` symbol text.
24    pub symbol: &'static str,
25    /// The citizen's encoding version.
26    pub version: u32,
27    /// The crate that defined the citizen.
28    pub crate_name: &'static str,
29    /// Number of constructor fields (excluding the version argument).
30    pub arity: usize,
31    /// Hook that installs the citizen's class into a linker.
32    pub install: InstallFn,
33    /// Hook that runs the citizen's conformance fixture.
34    pub conformance: ConformanceFn,
35}
36
37inventory::collect!(CitizenInfo);
38
39/// One inventory-registered explicit non-citizen exemption.
40///
41/// Collected via `inventory` so live handles and other deliberate exemptions
42/// are recorded alongside citizens rather than only living in source comments.
43#[derive(Clone, Copy)]
44pub struct NonCitizenInfo {
45    /// The Rust type name carrying the exemption.
46    pub type_name: &'static str,
47    /// The crate that defined the exemption.
48    pub crate_name: &'static str,
49    /// Why the type is exempt from citizen conformance.
50    pub reason: &'static str,
51    /// The exemption kind, for example `live-handle`.
52    pub kind: &'static str,
53    /// The named descriptor strategy the type follows instead.
54    pub descriptor: &'static str,
55}
56
57inventory::collect!(NonCitizenInfo);
58
59/// An explicit citizen registry built by naming the citizen types a crate owns.
60///
61/// Inventory remains available for ordinary host binaries, but strict gates and
62/// wasm/LTO builds can construct this registry directly with
63/// [`CitizenRegistry::register`]. That path references each citizen type in
64/// ordinary Rust code, so registration does not depend on link-time constructor
65/// retention.
66#[derive(Clone, Default)]
67pub struct CitizenRegistry {
68    citizens: Vec<CitizenInfo>,
69}
70
71impl CitizenRegistry {
72    /// Builds an empty explicit registry.
73    pub fn new() -> Self {
74        Self::default()
75    }
76
77    /// Builds an explicit registry from the current inventory rows.
78    ///
79    /// This is useful when a caller wants the registry APIs, such as
80    /// completeness checks or census rendering, while still using inventory as
81    /// the discovery source.
82    pub fn from_inventory() -> Result<Self> {
83        let mut registry = Self::new();
84        for info in registered_citizens() {
85            registry.register_info(*info)?;
86        }
87        Ok(registry)
88    }
89
90    /// Registers the citizen metadata generated for `T`.
91    ///
92    /// Calling this function is the DCE-safe path: the owning crate names every
93    /// citizen type it expects to ship, and the registry is then loadable as a
94    /// normal kernel [`Lib`].
95    pub fn register<T>(&mut self) -> Result<&mut Self>
96    where
97        T: CitizenRuntime,
98    {
99        self.register_info(T::citizen_info())
100    }
101
102    /// Registers one citizen metadata row.
103    ///
104    /// Duplicate symbols are rejected so an explicit registry cannot silently
105    /// shadow two distinct Rust types behind one SIM class symbol.
106    pub fn register_info(&mut self, info: CitizenInfo) -> Result<&mut Self> {
107        if self
108            .citizens
109            .iter()
110            .any(|existing| existing.symbol == info.symbol)
111        {
112            return Err(Error::Eval(format!(
113                "duplicate citizen registration for {}",
114                info.symbol
115            )));
116        }
117        self.citizens.push(info);
118        Ok(self)
119    }
120
121    /// Iterates the citizen rows in this explicit registry.
122    pub fn citizens(&self) -> impl Iterator<Item = &CitizenInfo> {
123        self.citizens.iter()
124    }
125
126    /// Returns the number of citizen rows in this explicit registry.
127    pub fn len(&self) -> usize {
128        self.citizens.len()
129    }
130
131    /// Reports whether this explicit registry has no citizen rows.
132    pub fn is_empty(&self) -> bool {
133        self.citizens.is_empty()
134    }
135
136    /// Returns every expected symbol missing from this registry.
137    pub fn missing_symbols<'a>(&self, expected: &'a [&'a str]) -> Vec<&'a str> {
138        let found = self
139            .citizens
140            .iter()
141            .map(|info| info.symbol)
142            .collect::<BTreeSet<_>>();
143        expected
144            .iter()
145            .copied()
146            .filter(|symbol| !found.contains(symbol))
147            .collect()
148    }
149
150    /// Fails closed unless this registry contains every expected symbol.
151    pub fn ensure_contains_symbols(&self, expected: &[&str]) -> Result<()> {
152        let missing = self.missing_symbols(expected);
153        if missing.is_empty() {
154            return Ok(());
155        }
156        Err(Error::HostError(format!(
157            "citizen registry incomplete: {} expected, {} registered; missing {:?}",
158            expected.len(),
159            self.len(),
160            missing
161        )))
162    }
163
164    /// Installs every citizen row in this registry into `linker`.
165    pub fn install_all(&self, linker: &mut Linker<'_>) -> Result<()> {
166        for info in self.citizens() {
167            (info.install)(linker)?;
168        }
169        Ok(())
170    }
171
172    /// Installs only the citizen rows whose symbols are in `namespace`.
173    pub fn install_namespace(&self, linker: &mut Linker<'_>, namespace: &str) -> Result<()> {
174        for info in self
175            .citizens()
176            .filter(|info| symbol_namespace(info.symbol) == Some(namespace))
177        {
178            (info.install)(linker)?;
179        }
180        Ok(())
181    }
182}
183
184impl Lib for CitizenRegistry {
185    fn manifest(&self) -> LibManifest {
186        LibManifest {
187            id: Symbol::qualified("citizen", "explicit"),
188            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
189            abi: AbiVersion { major: 0, minor: 1 },
190            target: LibTarget::HostRegistered,
191            requires: Vec::new(),
192            capabilities: Vec::new(),
193            exports: self
194                .citizens()
195                .map(|info| Export::Class {
196                    symbol: parse_symbol(info.symbol),
197                    class_id: None,
198                })
199                .collect(),
200        }
201    }
202
203    fn load(&self, _cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
204        self.install_all(linker)
205    }
206}
207
208/// Iterates every [`CitizenInfo`] registered through `inventory`.
209pub fn registered_citizens() -> impl Iterator<Item = &'static CitizenInfo> {
210    inventory::iter::<CitizenInfo>.into_iter()
211}
212
213/// Iterates every [`NonCitizenInfo`] registered through `inventory`.
214pub fn registered_non_citizens() -> impl Iterator<Item = &'static NonCitizenInfo> {
215    inventory::iter::<NonCitizenInfo>.into_iter()
216}
217
218/// Installs every registered citizen's class into `linker`.
219pub fn install_all(linker: &mut Linker<'_>) -> Result<()> {
220    for info in registered_citizens() {
221        (info.install)(linker)?;
222    }
223    Ok(())
224}
225
226/// Installs only the registered citizens whose symbol is in `namespace`.
227pub fn install_namespace(linker: &mut Linker<'_>, namespace: &str) -> Result<()> {
228    for info in
229        registered_citizens().filter(|info| symbol_namespace(info.symbol) == Some(namespace))
230    {
231        (info.install)(linker)?;
232    }
233    Ok(())
234}
235
236/// A kernel [`Lib`] that loads registered citizens into a context.
237///
238/// Either loads every registered citizen or only those in a chosen namespace.
239/// Its manifest exports one class per included citizen so the runtime can
240/// resolve them by symbol.
241#[derive(Clone, Copy, Debug, Default)]
242pub struct CitizenLib {
243    namespace: Option<&'static str>,
244}
245
246impl CitizenLib {
247    /// Returns a lib that loads every registered citizen.
248    pub fn all() -> Self {
249        Self { namespace: None }
250    }
251
252    /// Returns a lib that loads only citizens in `namespace`.
253    pub fn namespace(namespace: &'static str) -> Self {
254        Self {
255            namespace: Some(namespace),
256        }
257    }
258}
259
260impl Lib for CitizenLib {
261    fn manifest(&self) -> LibManifest {
262        let id = match self.namespace {
263            Some(namespace) => Symbol::qualified("citizen", namespace.to_owned()),
264            None => Symbol::qualified("citizen", "all"),
265        };
266        LibManifest {
267            id,
268            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
269            abi: AbiVersion { major: 0, minor: 1 },
270            target: LibTarget::HostRegistered,
271            requires: Vec::new(),
272            capabilities: Vec::new(),
273            exports: registered_citizens()
274                .filter(|info| match self.namespace {
275                    Some(namespace) => symbol_namespace(info.symbol) == Some(namespace),
276                    None => true,
277                })
278                .map(|info| Export::Class {
279                    symbol: parse_symbol(info.symbol),
280                    class_id: None,
281                })
282                .collect(),
283        }
284    }
285
286    fn load(&self, _cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
287        match self.namespace {
288            Some(namespace) => install_namespace(linker, namespace),
289            None => install_all(linker),
290        }
291    }
292}
293
294fn symbol_namespace(symbol: &str) -> Option<&str> {
295    symbol.split_once('/').map(|(namespace, _)| namespace)
296}