canic_memory/
registry.rs

1use crate::ThisError;
2use std::{cell::RefCell, collections::BTreeMap};
3
4///
5/// MemoryRange
6///
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub struct MemoryRange {
10    pub start: u8,
11    pub end: u8,
12}
13
14impl MemoryRange {
15    #[must_use]
16    pub const fn contains(&self, id: u8) -> bool {
17        id >= self.start && id <= self.end
18    }
19}
20
21///
22/// MemoryRegistryEntry
23///
24
25#[derive(Clone, Debug)]
26pub struct MemoryRegistryEntry {
27    pub crate_name: String,
28    pub label: String,
29}
30
31///
32/// MemoryRegistryError
33///
34
35#[derive(Debug, ThisError)]
36pub enum MemoryRegistryError {
37    #[error(
38        "memory range overlap: crate '{existing_crate}' [{existing_start}-{existing_end}]
39conflicts with crate '{new_crate}' [{new_start}-{new_end}]"
40    )]
41    Overlap {
42        existing_crate: String,
43        existing_start: u8,
44        existing_end: u8,
45        new_crate: String,
46        new_start: u8,
47        new_end: u8,
48    },
49
50    #[error("memory id {0} is already registered; each memory id must be globally unique")]
51    DuplicateId(u8),
52}
53
54//
55// Internal global state (substrate-level, single-threaded)
56//
57
58thread_local! {
59    static RESERVED_RANGES: RefCell<Vec<(String, MemoryRange)>> = const { RefCell::new(Vec::new()) };
60    static REGISTRY: RefCell<BTreeMap<u8, MemoryRegistryEntry>> = const { RefCell::new(BTreeMap::new()) };
61
62    // Deferred registrations (used before init)
63    static PENDING_RANGES: RefCell<Vec<(String, u8, u8)>> = const { RefCell::new(Vec::new()) };
64    static PENDING_REGISTRATIONS: RefCell<Vec<(u8, String, String)>> = const { RefCell::new(Vec::new()) };
65}
66
67///
68/// MemoryRegistry
69///
70/// Canonical substrate registry for stable memory IDs.
71///
72pub struct MemoryRegistry;
73
74impl MemoryRegistry {
75    /// Reserve a memory range for a crate.
76    pub fn reserve_range(crate_name: &str, start: u8, end: u8) -> Result<(), MemoryRegistryError> {
77        let range = MemoryRange { start, end };
78
79        RESERVED_RANGES.with_borrow(|ranges| {
80            for (existing_crate, existing_range) in ranges {
81                if ranges_overlap(*existing_range, range) {
82                    if existing_crate == crate_name
83                        && existing_range.start == start
84                        && existing_range.end == end
85                    {
86                        // Allow exact duplicate reservations for idempotent init.
87                        return Ok(());
88                    }
89                    return Err(MemoryRegistryError::Overlap {
90                        existing_crate: existing_crate.clone(),
91                        existing_start: existing_range.start,
92                        existing_end: existing_range.end,
93                        new_crate: crate_name.to_string(),
94                        new_start: start,
95                        new_end: end,
96                    });
97                }
98            }
99
100            Ok(())
101        })?;
102
103        RESERVED_RANGES.with_borrow_mut(|ranges| {
104            ranges.push((crate_name.to_string(), range));
105        });
106
107        Ok(())
108    }
109
110    /// Register a memory ID.
111    pub fn register(id: u8, crate_name: &str, label: &str) -> Result<(), MemoryRegistryError> {
112        REGISTRY.with_borrow(|reg| {
113            if reg.contains_key(&id) {
114                return Err(MemoryRegistryError::DuplicateId(id));
115            }
116            Ok(())
117        })?;
118
119        REGISTRY.with_borrow_mut(|reg| {
120            reg.insert(
121                id,
122                MemoryRegistryEntry {
123                    crate_name: crate_name.to_string(),
124                    label: label.to_string(),
125                },
126            );
127        });
128
129        Ok(())
130    }
131
132    /// Export all registered entries (canonical snapshot).
133    #[must_use]
134    pub fn export() -> Vec<(u8, MemoryRegistryEntry)> {
135        REGISTRY.with_borrow(|reg| reg.iter().map(|(k, v)| (*k, v.clone())).collect())
136    }
137
138    /// Export all reserved ranges.
139    #[must_use]
140    pub fn export_ranges() -> Vec<(String, MemoryRange)> {
141        RESERVED_RANGES.with_borrow(std::clone::Clone::clone)
142    }
143
144    /// Retrieve a single registry entry.
145    #[must_use]
146    pub fn get(id: u8) -> Option<MemoryRegistryEntry> {
147        REGISTRY.with_borrow(|reg| reg.get(&id).cloned())
148    }
149}
150
151//
152// Deferred registration helpers (used before runtime init)
153//
154
155pub fn defer_reserve_range(crate_name: &str, start: u8, end: u8) {
156    PENDING_RANGES.with_borrow_mut(|ranges| {
157        ranges.push((crate_name.to_string(), start, end));
158    });
159}
160
161pub fn defer_register(id: u8, crate_name: &str, label: &str) {
162    PENDING_REGISTRATIONS.with_borrow_mut(|regs| {
163        regs.push((id, crate_name.to_string(), label.to_string()));
164    });
165}
166
167#[must_use]
168pub fn drain_pending_ranges() -> Vec<(String, u8, u8)> {
169    PENDING_RANGES.with_borrow_mut(std::mem::take)
170}
171
172#[must_use]
173pub fn drain_pending_registrations() -> Vec<(u8, String, String)> {
174    PENDING_REGISTRATIONS.with_borrow_mut(std::mem::take)
175}
176
177//
178// Test-only helpers
179//
180
181#[cfg(test)]
182pub fn reset_for_tests() {
183    RESERVED_RANGES.with_borrow_mut(Vec::clear);
184    REGISTRY.with_borrow_mut(BTreeMap::clear);
185    PENDING_RANGES.with_borrow_mut(Vec::clear);
186    PENDING_REGISTRATIONS.with_borrow_mut(Vec::clear);
187}
188
189//
190// Internal helpers
191//
192
193const fn ranges_overlap(a: MemoryRange, b: MemoryRange) -> bool {
194    a.start <= b.end && b.start <= a.end
195}