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, MutexGuard};
10
11#[cfg(test)]
12pub 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 from a validated authority record.
73    #[must_use]
74    pub const fn new(record: MemoryManagerAuthorityRecord) -> Self {
75        Self { record }
76    }
77
78    /// Return the crate that registered this range.
79    #[must_use]
80    pub fn declaring_crate(&self) -> &str {
81        self.record.authority()
82    }
83
84    /// Borrow the authority record.
85    #[must_use]
86    pub const fn record(&self) -> &MemoryManagerAuthorityRecord {
87        &self.record
88    }
89
90    /// Consume this registration and return the authority record.
91    #[must_use]
92    pub fn into_record(self) -> MemoryManagerAuthorityRecord {
93        self.record
94    }
95}
96
97///
98/// StaticMemoryDeclarationError
99///
100/// Failure to register or collect static allocation declarations.
101#[non_exhaustive]
102#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
103pub enum StaticMemoryDeclarationError {
104    /// Static declaration registry lock was poisoned.
105    #[error("static memory declaration registry lock poisoned")]
106    RegistryPoisoned,
107    /// Bootstrap already sealed the declaration snapshot.
108    #[error("static memory declaration registry is already sealed")]
109    RegistrySealed,
110    /// Declaration validation failed.
111    #[error(transparent)]
112    Declaration(#[from] crate::DeclarationSnapshotError),
113    /// Range authority validation failed.
114    #[error(transparent)]
115    Range(#[from] MemoryManagerRangeAuthorityError),
116}
117
118#[derive(Debug, Default)]
119struct StaticMemoryDeclarationRegistry {
120    declarations: Vec<StaticMemoryDeclaration>,
121    ranges: Vec<StaticMemoryRangeDeclaration>,
122    sealed: bool,
123}
124
125static STATIC_MEMORY_DECLARATIONS: Mutex<StaticMemoryDeclarationRegistry> =
126    Mutex::new(StaticMemoryDeclarationRegistry {
127        declarations: Vec::new(),
128        ranges: Vec::new(),
129        sealed: false,
130    });
131
132fn lock_registry()
133-> Result<MutexGuard<'static, StaticMemoryDeclarationRegistry>, StaticMemoryDeclarationError> {
134    STATIC_MEMORY_DECLARATIONS
135        .lock()
136        .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)
137}
138
139const fn ensure_unsealed(
140    registry: &StaticMemoryDeclarationRegistry,
141) -> Result<(), StaticMemoryDeclarationError> {
142    if registry.sealed {
143        return Err(StaticMemoryDeclarationError::RegistrySealed);
144    }
145    Ok(())
146}
147
148fn with_unsealed_registry(
149    op: impl FnOnce(&mut StaticMemoryDeclarationRegistry),
150) -> Result<(), StaticMemoryDeclarationError> {
151    let mut registry = lock_registry()?;
152    ensure_unsealed(&registry)?;
153    op(&mut registry);
154    Ok(())
155}
156
157/// Register one allocation declaration before bootstrap seals the snapshot.
158pub fn register_static_memory_declaration(
159    declaring_crate: impl Into<String>,
160    declaration: AllocationDeclaration,
161) -> Result<(), StaticMemoryDeclarationError> {
162    with_unsealed_registry(|registry| {
163        registry
164            .declarations
165            .push(StaticMemoryDeclaration::new(declaring_crate, declaration));
166    })
167}
168
169/// Register one `MemoryManager` authority range before bootstrap seals the snapshot.
170pub fn register_static_memory_manager_range(
171    start: u8,
172    end: u8,
173    declaring_crate: impl Into<String>,
174    mode: MemoryManagerRangeMode,
175    purpose: Option<String>,
176) -> Result<(), StaticMemoryDeclarationError> {
177    let declaring_crate = declaring_crate.into();
178    let record = MemoryManagerAuthorityRecord::new(
179        MemoryManagerIdRange::new(start, end).map_err(MemoryManagerRangeAuthorityError::Range)?,
180        declaring_crate,
181        mode,
182        purpose,
183    )?;
184    register_static_memory_range_declaration(StaticMemoryRangeDeclaration::new(record))
185}
186
187/// Register one authority range declaration before bootstrap seals the snapshot.
188pub fn register_static_memory_range_declaration(
189    declaration: StaticMemoryRangeDeclaration,
190) -> Result<(), StaticMemoryDeclarationError> {
191    with_unsealed_registry(|registry| {
192        registry.ranges.push(declaration);
193    })
194}
195
196/// Register one `MemoryManager` declaration before bootstrap seals the snapshot.
197pub fn register_static_memory_manager_declaration(
198    id: u8,
199    declaring_crate: impl Into<String>,
200    label: impl Into<String>,
201    stable_key: impl AsRef<str>,
202) -> Result<(), StaticMemoryDeclarationError> {
203    register_static_memory_manager_declaration_with_schema(
204        id,
205        declaring_crate,
206        label,
207        stable_key,
208        SchemaMetadata::default(),
209    )
210}
211
212/// Register one `MemoryManager` declaration with schema metadata.
213pub fn register_static_memory_manager_declaration_with_schema(
214    id: u8,
215    declaring_crate: impl Into<String>,
216    label: impl Into<String>,
217    stable_key: impl AsRef<str>,
218    schema: SchemaMetadata,
219) -> Result<(), StaticMemoryDeclarationError> {
220    let declaration =
221        AllocationDeclaration::memory_manager_with_schema(stable_key, id, label, schema)?;
222    register_static_memory_declaration(declaring_crate, declaration)
223}
224
225/// Return the currently registered static allocation declarations.
226pub fn static_memory_declarations()
227-> Result<Vec<StaticMemoryDeclaration>, StaticMemoryDeclarationError> {
228    Ok(lock_registry()?.declarations.clone())
229}
230
231/// Return the currently registered static range declarations.
232pub fn static_memory_range_declarations()
233-> Result<Vec<StaticMemoryRangeDeclaration>, StaticMemoryDeclarationError> {
234    Ok(lock_registry()?.ranges.clone())
235}
236
237/// Return the currently registered static range declarations as an authority table.
238pub fn static_memory_range_authority()
239-> Result<MemoryManagerRangeAuthority, StaticMemoryDeclarationError> {
240    MemoryManagerRangeAuthority::from_records(
241        static_memory_range_declarations()?
242            .into_iter()
243            .map(StaticMemoryRangeDeclaration::into_record)
244            .collect(),
245    )
246    .map_err(StaticMemoryDeclarationError::Range)
247}
248
249/// Seal the static memory registry so later registration attempts fail closed.
250pub fn seal_static_memory_registry() -> Result<(), StaticMemoryDeclarationError> {
251    let mut registry = lock_registry()?;
252    registry.sealed = true;
253    Ok(())
254}
255
256/// Add currently registered static allocation declarations to a collector.
257pub fn collect_static_memory_declarations(
258    collector: &mut DeclarationCollector,
259) -> Result<(), StaticMemoryDeclarationError> {
260    for registration in static_memory_declarations()? {
261        collector.push(registration.into_declaration());
262    }
263    Ok(())
264}
265
266/// Seal currently registered static allocation declarations into a snapshot.
267///
268/// Sealing prevents later static registrations from being accepted. Callers may
269/// still call this function again to rebuild the same snapshot for idempotent
270/// bootstrap paths.
271pub fn static_memory_declaration_snapshot()
272-> Result<DeclarationSnapshot, StaticMemoryDeclarationError> {
273    let declarations = {
274        let mut registry = lock_registry()?;
275        registry.sealed = true;
276        registry
277            .declarations
278            .iter()
279            .map(|registration| registration.declaration.clone())
280            .collect()
281    };
282    DeclarationSnapshot::new(declarations).map_err(StaticMemoryDeclarationError::Declaration)
283}
284
285#[cfg(test)]
286pub fn reset_static_memory_declarations_for_tests() {
287    let mut registry = STATIC_MEMORY_DECLARATIONS
288        .lock()
289        .expect("static memory declaration registry poisoned");
290    registry.declarations.clear();
291    registry.ranges.clear();
292    registry.sealed = false;
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298
299    #[test]
300    fn registers_and_seals_static_memory_declarations() {
301        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
302        reset_static_memory_declarations_for_tests();
303
304        register_static_memory_manager_declaration(100, "icydb", "users", "icydb.users.data.v1")
305            .expect("register declaration");
306
307        let registrations = static_memory_declarations().expect("registrations");
308        assert_eq!(registrations.len(), 1);
309        assert_eq!(registrations[0].declaring_crate(), "icydb");
310        assert_eq!(
311            registrations[0].declaration().stable_key().as_str(),
312            "icydb.users.data.v1"
313        );
314
315        let snapshot = static_memory_declaration_snapshot().expect("snapshot");
316        assert_eq!(snapshot.len(), 1);
317
318        let err =
319            register_static_memory_manager_declaration(101, "icydb", "orders", "icydb.orders.v1")
320                .expect_err("late registration must fail");
321        assert_eq!(err, StaticMemoryDeclarationError::RegistrySealed);
322    }
323
324    #[test]
325    fn registers_static_memory_ranges() {
326        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
327        reset_static_memory_declarations_for_tests();
328
329        register_static_memory_manager_range(
330            100,
331            109,
332            "crate_a",
333            MemoryManagerRangeMode::Reserved,
334            Some("crate A stores".to_string()),
335        )
336        .expect("register range");
337
338        let ranges = static_memory_range_declarations().expect("ranges");
339        assert_eq!(ranges.len(), 1);
340        assert_eq!(ranges[0].declaring_crate(), "crate_a");
341        assert_eq!(ranges[0].record().range().start(), 100);
342        assert_eq!(ranges[0].record().range().end(), 109);
343    }
344
345    #[test]
346    fn static_range_declaration_uses_record_authority() {
347        let record = MemoryManagerAuthorityRecord::new(
348            MemoryManagerIdRange::new(100, 109).expect("range"),
349            "record_authority",
350            MemoryManagerRangeMode::Reserved,
351            None,
352        )
353        .expect("record");
354
355        let range = StaticMemoryRangeDeclaration::new(record);
356
357        assert_eq!(range.declaring_crate(), "record_authority");
358    }
359
360    #[test]
361    fn snapshot_rejects_duplicate_static_memory_declarations() {
362        let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
363        reset_static_memory_declarations_for_tests();
364
365        register_static_memory_manager_declaration(100, "icydb", "users", "icydb.users.data.v1")
366            .expect("register first declaration");
367        register_static_memory_manager_declaration(100, "icydb", "orders", "icydb.orders.v1")
368            .expect("register duplicate slot declaration");
369
370        let err = static_memory_declaration_snapshot().expect_err("duplicate slot must fail");
371        assert!(matches!(
372            err,
373            StaticMemoryDeclarationError::Declaration(
374                crate::DeclarationSnapshotError::DuplicateSlot(_)
375            )
376        ));
377    }
378}