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