Skip to main content

ic_memory/
registry.rs

1use crate::{
2    declaration::{AllocationDeclaration, DeclarationCollector, DeclarationSnapshot},
3    schema::SchemaMetadata,
4    slot::{
5        MemoryManagerAuthorityRecord, MemoryManagerIdRange, MemoryManagerRangeAuthority,
6        MemoryManagerRangeAuthorityError, MemoryManagerRangeMode,
7    },
8};
9use std::sync::Mutex;
10
11#[cfg(test)]
12pub(crate) static TEST_REGISTRY_LOCK: Mutex<()> = Mutex::new(());
13
14///
15/// StaticMemoryDeclaration
16///
17/// One allocation declaration registered by crate-level generated or macro
18/// code before bootstrap seals the declaration snapshot.
19///
20/// The `declaring_crate` field is policy metadata for integration layers such
21/// as Canic or IcyDB. The default runtime uses it to match declarations against
22/// registered range claims before it calls the caller's
23/// [`crate::AllocationPolicy`].
24#[derive(Clone, Debug, Eq, PartialEq)]
25pub struct StaticMemoryDeclaration {
26    declaring_crate: String,
27    declaration: AllocationDeclaration,
28}
29
30impl StaticMemoryDeclaration {
31    /// Build one static declaration from raw parts.
32    pub fn new(declaring_crate: impl Into<String>, declaration: AllocationDeclaration) -> Self {
33        Self {
34            declaring_crate: declaring_crate.into(),
35            declaration,
36        }
37    }
38
39    /// Return the crate that registered this declaration.
40    #[must_use]
41    pub fn declaring_crate(&self) -> &str {
42        &self.declaring_crate
43    }
44
45    /// Borrow the allocation declaration.
46    #[must_use]
47    pub const fn declaration(&self) -> &AllocationDeclaration {
48        &self.declaration
49    }
50
51    /// Consume this registration and return the allocation declaration.
52    #[must_use]
53    pub fn into_declaration(self) -> AllocationDeclaration {
54        self.declaration
55    }
56}
57
58///
59/// StaticMemoryRangeDeclaration
60///
61/// One `MemoryManager` authority range registered by crate-level generated or
62/// macro code before bootstrap seals the declaration snapshot. In the default
63/// runtime, registered user ranges are authoritative generic range policy:
64/// declarations must stay inside the declaring crate's claimed range before
65/// caller-supplied policy runs.
66#[derive(Clone, Debug, Eq, PartialEq)]
67pub struct StaticMemoryRangeDeclaration {
68    record: MemoryManagerAuthorityRecord,
69}
70
71impl StaticMemoryRangeDeclaration {
72    /// Build one static range declaration.
73    #[must_use]
74    pub fn new(declaring_crate: impl Into<String>, record: MemoryManagerAuthorityRecord) -> Self {
75        let declaring_crate = declaring_crate.into();
76        debug_assert_eq!(declaring_crate, record.authority);
77        Self { record }
78    }
79
80    /// Return the crate that registered this range.
81    #[must_use]
82    pub fn declaring_crate(&self) -> &str {
83        &self.record.authority
84    }
85
86    /// Borrow the authority record.
87    #[must_use]
88    pub const fn record(&self) -> &MemoryManagerAuthorityRecord {
89        &self.record
90    }
91
92    /// Consume this registration and return the authority record.
93    #[must_use]
94    pub fn into_record(self) -> MemoryManagerAuthorityRecord {
95        self.record
96    }
97}
98
99///
100/// StaticMemoryDeclarationError
101///
102/// Failure to register or collect static allocation declarations.
103#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
104pub enum StaticMemoryDeclarationError {
105    /// Static declaration registry lock was poisoned.
106    #[error("static memory declaration registry lock poisoned")]
107    RegistryPoisoned,
108    /// Bootstrap already sealed the declaration snapshot.
109    #[error("static memory declaration registry is already sealed")]
110    RegistrySealed,
111    /// Declaration validation failed.
112    #[error(transparent)]
113    Declaration(#[from] crate::DeclarationSnapshotError),
114    /// Range authority validation failed.
115    #[error(transparent)]
116    Range(#[from] MemoryManagerRangeAuthorityError),
117}
118
119#[derive(Debug, Default)]
120struct StaticMemoryDeclarationRegistry {
121    declarations: Vec<StaticMemoryDeclaration>,
122    ranges: Vec<StaticMemoryRangeDeclaration>,
123    sealed: bool,
124}
125
126static STATIC_MEMORY_DECLARATIONS: Mutex<StaticMemoryDeclarationRegistry> =
127    Mutex::new(StaticMemoryDeclarationRegistry {
128        declarations: Vec::new(),
129        ranges: Vec::new(),
130        sealed: false,
131    });
132
133/// Register one allocation declaration before bootstrap seals the snapshot.
134pub fn register_static_memory_declaration(
135    declaring_crate: impl Into<String>,
136    declaration: AllocationDeclaration,
137) -> Result<(), StaticMemoryDeclarationError> {
138    let mut registry = STATIC_MEMORY_DECLARATIONS
139        .lock()
140        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)?;
141    if registry.sealed {
142        return Err(StaticMemoryDeclarationError::RegistrySealed);
143    }
144    registry
145        .declarations
146        .push(StaticMemoryDeclaration::new(declaring_crate, declaration));
147    Ok(())
148}
149
150/// Register one `MemoryManager` authority range before bootstrap seals the snapshot.
151pub fn register_static_memory_manager_range(
152    start: u8,
153    end: u8,
154    declaring_crate: impl Into<String>,
155    mode: MemoryManagerRangeMode,
156    purpose: Option<String>,
157) -> Result<(), StaticMemoryDeclarationError> {
158    let declaring_crate = declaring_crate.into();
159    let record = MemoryManagerAuthorityRecord::new(
160        MemoryManagerIdRange::new(start, end).map_err(MemoryManagerRangeAuthorityError::Range)?,
161        declaring_crate.clone(),
162        mode,
163        purpose,
164    )?;
165    register_static_memory_range_declaration(StaticMemoryRangeDeclaration::new(
166        declaring_crate,
167        record,
168    ))
169}
170
171/// Register one authority range declaration before bootstrap seals the snapshot.
172pub fn register_static_memory_range_declaration(
173    declaration: StaticMemoryRangeDeclaration,
174) -> Result<(), StaticMemoryDeclarationError> {
175    let mut registry = STATIC_MEMORY_DECLARATIONS
176        .lock()
177        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)?;
178    if registry.sealed {
179        return Err(StaticMemoryDeclarationError::RegistrySealed);
180    }
181    registry.ranges.push(declaration);
182    Ok(())
183}
184
185/// Register one `MemoryManager` declaration before bootstrap seals the snapshot.
186pub fn register_static_memory_manager_declaration(
187    id: u8,
188    declaring_crate: impl Into<String>,
189    label: impl Into<String>,
190    stable_key: impl AsRef<str>,
191) -> Result<(), StaticMemoryDeclarationError> {
192    register_static_memory_manager_declaration_with_schema(
193        id,
194        declaring_crate,
195        label,
196        stable_key,
197        SchemaMetadata::default(),
198    )
199}
200
201/// Register one `MemoryManager` declaration with schema metadata.
202pub fn register_static_memory_manager_declaration_with_schema(
203    id: u8,
204    declaring_crate: impl Into<String>,
205    label: impl Into<String>,
206    stable_key: impl AsRef<str>,
207    schema: SchemaMetadata,
208) -> Result<(), StaticMemoryDeclarationError> {
209    let declaration =
210        AllocationDeclaration::memory_manager_with_schema(stable_key, id, label, schema)?;
211    register_static_memory_declaration(declaring_crate, declaration)
212}
213
214/// Return the currently registered static allocation declarations.
215pub fn static_memory_declarations()
216-> Result<Vec<StaticMemoryDeclaration>, StaticMemoryDeclarationError> {
217    STATIC_MEMORY_DECLARATIONS
218        .lock()
219        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)
220        .map(|registry| registry.declarations.clone())
221}
222
223/// Return the currently registered static range declarations.
224pub fn static_memory_range_declarations()
225-> Result<Vec<StaticMemoryRangeDeclaration>, StaticMemoryDeclarationError> {
226    STATIC_MEMORY_DECLARATIONS
227        .lock()
228        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)
229        .map(|registry| registry.ranges.clone())
230}
231
232/// Return the currently registered static range declarations as an authority table.
233pub fn static_memory_range_authority()
234-> Result<MemoryManagerRangeAuthority, StaticMemoryDeclarationError> {
235    MemoryManagerRangeAuthority::from_records(
236        static_memory_range_declarations()?
237            .into_iter()
238            .map(StaticMemoryRangeDeclaration::into_record)
239            .collect(),
240    )
241    .map_err(StaticMemoryDeclarationError::Range)
242}
243
244/// Seal the static memory registry so later registration attempts fail closed.
245pub(crate) fn seal_static_memory_registry() -> Result<(), StaticMemoryDeclarationError> {
246    let mut registry = STATIC_MEMORY_DECLARATIONS
247        .lock()
248        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)?;
249    registry.sealed = true;
250    Ok(())
251}
252
253/// Add currently registered static allocation declarations to a collector.
254pub fn collect_static_memory_declarations(
255    collector: &mut DeclarationCollector,
256) -> Result<(), StaticMemoryDeclarationError> {
257    for registration in static_memory_declarations()? {
258        collector.push(registration.into_declaration());
259    }
260    Ok(())
261}
262
263/// Seal currently registered static allocation declarations into a snapshot.
264///
265/// Sealing prevents later static registrations from being accepted. Callers may
266/// still call this function again to rebuild the same snapshot for idempotent
267/// bootstrap paths.
268pub fn static_memory_declaration_snapshot()
269-> Result<DeclarationSnapshot, StaticMemoryDeclarationError> {
270    let declarations = {
271        let mut registry = STATIC_MEMORY_DECLARATIONS
272            .lock()
273            .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)?;
274        registry.sealed = true;
275        registry
276            .declarations
277            .iter()
278            .map(|registration| registration.declaration.clone())
279            .collect()
280    };
281    DeclarationSnapshot::new(declarations).map_err(StaticMemoryDeclarationError::Declaration)
282}
283
284#[cfg(test)]
285pub(crate) fn reset_static_memory_declarations_for_tests() {
286    let mut registry = STATIC_MEMORY_DECLARATIONS
287        .lock()
288        .expect("static memory declaration registry poisoned");
289    registry.declarations.clear();
290    registry.ranges.clear();
291    registry.sealed = false;
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297
298    #[test]
299    fn registers_and_seals_static_memory_declarations() {
300        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
301        reset_static_memory_declarations_for_tests();
302
303        register_static_memory_manager_declaration(100, "icydb", "users", "icydb.users.data.v1")
304            .expect("register declaration");
305
306        let registrations = static_memory_declarations().expect("registrations");
307        assert_eq!(registrations.len(), 1);
308        assert_eq!(registrations[0].declaring_crate(), "icydb");
309        assert_eq!(
310            registrations[0].declaration().stable_key().as_str(),
311            "icydb.users.data.v1"
312        );
313
314        let snapshot = static_memory_declaration_snapshot().expect("snapshot");
315        assert_eq!(snapshot.len(), 1);
316
317        let err =
318            register_static_memory_manager_declaration(101, "icydb", "orders", "icydb.orders.v1")
319                .expect_err("late registration must fail");
320        assert_eq!(err, StaticMemoryDeclarationError::RegistrySealed);
321    }
322
323    #[test]
324    fn registers_static_memory_ranges() {
325        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
326        reset_static_memory_declarations_for_tests();
327
328        register_static_memory_manager_range(
329            100,
330            109,
331            "crate_a",
332            MemoryManagerRangeMode::Reserved,
333            Some("crate A stores".to_string()),
334        )
335        .expect("register range");
336
337        let ranges = static_memory_range_declarations().expect("ranges");
338        assert_eq!(ranges.len(), 1);
339        assert_eq!(ranges[0].declaring_crate(), "crate_a");
340        assert_eq!(ranges[0].record().range.start(), 100);
341        assert_eq!(ranges[0].record().range.end(), 109);
342    }
343
344    #[test]
345    fn snapshot_rejects_duplicate_static_memory_declarations() {
346        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
347        reset_static_memory_declarations_for_tests();
348
349        register_static_memory_manager_declaration(100, "icydb", "users", "icydb.users.data.v1")
350            .expect("register first declaration");
351        register_static_memory_manager_declaration(100, "icydb", "orders", "icydb.orders.v1")
352            .expect("register duplicate slot declaration");
353
354        let err = static_memory_declaration_snapshot().expect_err("duplicate slot must fail");
355        assert!(matches!(
356            err,
357            StaticMemoryDeclarationError::Declaration(
358                crate::DeclarationSnapshotError::DuplicateSlot(_)
359            )
360        ));
361    }
362}