Skip to main content

canic_memory/runtime/
registry.rs

1use crate::registry::{
2    MemoryRange, MemoryRangeEntry, MemoryRangeSnapshot, MemoryRegistry, MemoryRegistryEntry,
3    MemoryRegistryError, drain_pending_ranges, drain_pending_registrations,
4};
5use std::cell::Cell;
6
7thread_local! {
8    static MEMORY_REGISTRY_INITIALIZED: Cell<bool> = const { Cell::new(false) };
9}
10
11///
12/// MemoryRegistryInitSummary
13///
14/// Substrate-level summary of registry state after initialization.
15/// This is intended for diagnostics and testing only.
16/// It is NOT a stable API contract or external view.
17///
18
19#[derive(Debug)]
20pub struct MemoryRegistryInitSummary {
21    pub ranges: Vec<(String, MemoryRange)>,
22    pub entries: Vec<(u8, MemoryRegistryEntry)>,
23}
24
25///
26/// MemoryRegistryRuntime
27///
28/// Substrate runtime controller responsible for initializing the
29/// global memory registry.
30///
31/// This type performs mechanical coordination only:
32/// - ordering
33/// - conflict detection
34/// - idempotent initialization
35///
36/// It encodes no application semantics.
37///
38pub struct MemoryRegistryRuntime;
39
40impl MemoryRegistryRuntime {
41    /// Initialize the memory registry.
42    ///
43    /// - Optionally reserves an initial range for the caller.
44    /// - Applies all deferred range reservations.
45    /// - Applies all deferred ID registrations.
46    ///
47    /// This function is idempotent for the same initial range.
48    pub fn init(
49        initial_range: Option<(&str, u8, u8)>,
50    ) -> Result<MemoryRegistryInitSummary, MemoryRegistryError> {
51        // Reserve the caller's initial range first (if provided)
52        if let Some((crate_name, start, end)) = initial_range {
53            MemoryRegistry::reserve_range(crate_name, start, end)?;
54        }
55
56        // Apply deferred range reservations deterministically
57        let mut ranges = drain_pending_ranges();
58        ranges.sort_by_key(|(_, start, _)| *start);
59        for (crate_name, start, end) in ranges {
60            MemoryRegistry::reserve_range(&crate_name, start, end)?;
61        }
62
63        // Apply deferred registrations deterministically
64        let mut regs = drain_pending_registrations();
65        regs.sort_by_key(|(id, _, _)| *id);
66        for (id, crate_name, label) in regs {
67            MemoryRegistry::register(id, &crate_name, &label)?;
68        }
69
70        let summary = MemoryRegistryInitSummary {
71            ranges: MemoryRegistry::export_ranges(),
72            entries: MemoryRegistry::export(),
73        };
74        MEMORY_REGISTRY_INITIALIZED.with(|ready| ready.set(true));
75
76        Ok(summary)
77    }
78
79    #[must_use]
80    pub fn is_initialized() -> bool {
81        MEMORY_REGISTRY_INITIALIZED.with(Cell::get)
82    }
83
84    /// Snapshot all registry entries.
85    #[must_use]
86    pub fn snapshot_entries() -> Vec<(u8, MemoryRegistryEntry)> {
87        MemoryRegistry::export()
88    }
89
90    /// Snapshot all reserved memory ranges.
91    #[must_use]
92    pub fn snapshot_ranges() -> Vec<(String, MemoryRange)> {
93        MemoryRegistry::export_ranges()
94    }
95
96    /// Snapshot all reserved memory ranges with owners.
97    #[must_use]
98    pub fn snapshot_range_entries() -> Vec<MemoryRangeEntry> {
99        MemoryRegistry::export_range_entries()
100    }
101
102    /// Snapshot registry entries grouped by range.
103    #[must_use]
104    pub fn snapshot_ids_by_range() -> Vec<MemoryRangeSnapshot> {
105        MemoryRegistry::export_ids_by_range()
106    }
107
108    /// Retrieve a single registry entry by ID.
109    #[must_use]
110    pub fn get(id: u8) -> Option<MemoryRegistryEntry> {
111        MemoryRegistry::get(id)
112    }
113
114    /// Apply any newly deferred registrations/ranges after runtime init.
115    ///
116    /// This is a no-op until initialization has completed. Once initialized,
117    /// this drains pending range/ID registrations so lazily touched statics can
118    /// become visible during the same request.
119    pub fn commit_pending_if_initialized() -> Result<(), MemoryRegistryError> {
120        if !Self::is_initialized() || crate::runtime::is_eager_tls_initializing() {
121            return Ok(());
122        }
123
124        let _ = Self::init(None)?;
125        Ok(())
126    }
127}
128
129///
130/// TESTS
131///
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use crate::registry::{defer_register, defer_reserve_range, reset_for_tests};
137
138    #[test]
139    fn init_applies_initial_and_pending() {
140        reset_for_tests();
141        defer_reserve_range("crate_b", 5, 6);
142        defer_register(5, "crate_b", "B5");
143
144        let summary =
145            MemoryRegistryRuntime::init(Some(("crate_a", 1, 3))).expect("init should succeed");
146
147        assert_eq!(summary.ranges.len(), 2);
148        assert_eq!(summary.entries.len(), 1);
149        assert_eq!(summary.entries[0].0, 5);
150        assert_eq!(summary.entries[0].1.label, "B5");
151    }
152
153    #[test]
154    fn init_is_idempotent_for_same_initial_range() {
155        reset_for_tests();
156
157        MemoryRegistryRuntime::init(Some(("crate_a", 1, 3))).expect("first init should succeed");
158        MemoryRegistryRuntime::init(Some(("crate_a", 1, 3))).expect("second init should succeed");
159    }
160
161    #[test]
162    fn init_returns_error_on_conflict() {
163        reset_for_tests();
164        defer_reserve_range("crate_a", 1, 3);
165        defer_reserve_range("crate_b", 3, 4);
166
167        let err = MemoryRegistryRuntime::init(None).unwrap_err();
168        assert!(matches!(err, MemoryRegistryError::Overlap { .. }));
169    }
170
171    #[test]
172    fn commit_pending_after_init_applies_late_deferred_items() {
173        reset_for_tests();
174
175        MemoryRegistryRuntime::init(Some(("core", 1, 10))).expect("init should succeed");
176        defer_reserve_range("late", 20, 30);
177        defer_register(22, "late", "late_slot");
178
179        MemoryRegistryRuntime::commit_pending_if_initialized()
180            .expect("late pending commit should succeed");
181
182        let entry = MemoryRegistryRuntime::get(22).expect("late entry should be registered");
183        assert_eq!(entry.crate_name, "late");
184        assert_eq!(entry.label, "late_slot");
185    }
186}