Skip to main content

telepath_server/
resource.rs

1use core::any::TypeId;
2use core::cell::UnsafeCell;
3use core::mem::{self, MaybeUninit};
4
5const MAX_RESOURCES: usize = 8;
6const STORAGE_SIZE: usize = 128;
7
8struct Entry {
9    type_id: TypeId,
10    offset: usize,
11    drop_fn: unsafe fn(*mut u8),
12}
13
14// Guarantees the storage buffer is at least 8-byte aligned so that casting
15// `base.add(offset)` to `*mut T` is valid for any T with align_of::<T>() <= 8,
16// which covers all primitive and peripheral-handle types used in embedded targets.
17#[repr(align(8))]
18struct AlignedStorage([MaybeUninit<u8>; STORAGE_SIZE]);
19
20pub struct ResourceRegistry {
21    entries: [MaybeUninit<Entry>; MAX_RESOURCES],
22    count: usize,
23    storage: UnsafeCell<AlignedStorage>,
24    used: usize,
25}
26
27impl Default for ResourceRegistry {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33// Monomorphised drop shim: restores the concrete type from a raw byte pointer
34// and calls its destructor. Stored once per Entry at insert time so the
35// registry can drop values without retaining generic type information.
36unsafe fn drop_in_place_erased<T>(ptr: *mut u8) {
37    // SAFETY: caller guarantees `ptr` was produced by `core::ptr::write::<T>`
38    // into aligned storage owned by this registry and is still initialised.
39    unsafe { core::ptr::drop_in_place(ptr as *mut T) }
40}
41
42impl ResourceRegistry {
43    pub const fn new() -> Self {
44        Self {
45            entries: [const { MaybeUninit::uninit() }; MAX_RESOURCES],
46            count: 0,
47            storage: UnsafeCell::new(AlignedStorage(
48                [const { MaybeUninit::uninit() }; STORAGE_SIZE],
49            )),
50            used: 0,
51        }
52    }
53
54    /// Move `val` into the registry, keyed by its `TypeId`.
55    ///
56    /// Inserted resources are dropped in reverse insertion order when the
57    /// registry itself is dropped, matching Rust's standard field/local
58    /// destruction order.
59    ///
60    /// # Alignment
61    ///
62    /// `T` must have `align_of::<T>() <= 8`. `AlignedStorage` guarantees an
63    /// 8-byte-aligned base; types with stricter alignment requirements cannot be
64    /// stored safely and will cause a panic at insertion time.
65    ///
66    /// # Panics
67    ///
68    /// Panics if a resource of the same `TypeId` has already been inserted, if
69    /// the registry is full (more than `MAX_RESOURCES` entries or `STORAGE_SIZE`
70    /// bytes used), or if `align_of::<T>() > 8`.
71    pub fn insert<T: 'static>(&mut self, val: T) {
72        // Duplicate TypeId check — fail-fast so silent shadowing is impossible.
73        // This also acts as a runtime backstop for compile-time dedup checks in
74        // the proc-macro that may miss type aliases or differently-spelled paths.
75        let id = TypeId::of::<T>();
76        for i in 0..self.count {
77            // SAFETY: entries[0..count] are initialised by insert().
78            let entry = unsafe { self.entries[i].assume_init_ref() };
79            if entry.type_id == id {
80                panic!("duplicate resource type: each resource type may appear at most once");
81            }
82        }
83
84        let align = mem::align_of::<T>();
85        let size = mem::size_of::<T>();
86
87        // AlignedStorage is repr(align(8)); types with stricter alignment would
88        // produce mis-aligned pointers. Catch this at insertion time.
89        assert!(
90            align <= 8,
91            "resource type alignment {} exceeds AlignedStorage alignment (8)",
92            align,
93        );
94
95        let offset = (self.used + align - 1) & !(align - 1);
96
97        assert!(
98            offset + size <= STORAGE_SIZE,
99            "resource storage full ({} + {} > {})",
100            offset,
101            size,
102            STORAGE_SIZE,
103        );
104        assert!(
105            self.count < MAX_RESOURCES,
106            "too many resources (max {})",
107            MAX_RESOURCES,
108        );
109
110        // SAFETY: `offset` is within the buffer and `offset % align_of::<T>() == 0`.
111        // AlignedStorage guarantees the base pointer is 8-byte aligned; T's alignment
112        // is at most 8 (asserted above).
113        unsafe {
114            let base = (*self.storage.get()).0.as_mut_ptr();
115            let dst = base.add(offset) as *mut T;
116            core::ptr::write(dst, val);
117        }
118
119        self.entries[self.count].write(Entry {
120            type_id: TypeId::of::<T>(),
121            offset,
122            drop_fn: drop_in_place_erased::<T>,
123        });
124        self.count += 1;
125        self.used = offset + size;
126    }
127
128    /// Look up a resource by type, returning a raw pointer.
129    ///
130    /// # Safety contract for callers
131    ///
132    /// The returned pointer is valid for the lifetime of the registry. Creating
133    /// a `&mut T` from it is safe provided:
134    /// - Each concrete type is registered at most once (enforced by `insert`).
135    /// - No two live `&mut` references alias the same entry.
136    /// - Dispatch is single-threaded (one shim runs at a time).
137    pub fn get_ptr<T: 'static>(&self) -> Option<*mut T> {
138        let id = TypeId::of::<T>();
139        for i in 0..self.count {
140            // SAFETY: entries[0..count] are initialised by insert().
141            let entry = unsafe { self.entries[i].assume_init_ref() };
142            if entry.type_id == id {
143                // SAFETY: offset was recorded by insert(); UnsafeCell allows
144                // interior mutation through a shared reference.
145                let ptr = unsafe {
146                    let base = (*self.storage.get()).0.as_mut_ptr();
147                    base.add(entry.offset) as *mut T
148                };
149                return Some(ptr);
150            }
151        }
152        None
153    }
154}
155
156impl Drop for ResourceRegistry {
157    fn drop(&mut self) {
158        // Drop in reverse insertion order — matches Rust's standard destruction
159        // order for fields/locals so that resources registered later (which may
160        // logically depend on earlier ones) are torn down first.
161        for i in (0..self.count).rev() {
162            // SAFETY: entries[0..self.count] were all initialised by insert().
163            let entry = unsafe { self.entries[i].assume_init_ref() };
164            // SAFETY: storage at entry.offset holds an initialised T whose
165            // drop_fn was recorded by insert(). The registry has exclusive
166            // ownership at drop time — no shim borrow can overlap because
167            // `&mut self` is required and dispatch only holds `&self`.
168            unsafe {
169                let base = (*self.storage.get()).0.as_mut_ptr() as *mut u8;
170                (entry.drop_fn)(base.add(entry.offset));
171            }
172        }
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179    use core::sync::atomic::{AtomicUsize, Ordering};
180
181    #[test]
182    fn insert_and_get_ptr() {
183        let mut reg = ResourceRegistry::new();
184        reg.insert(42u32);
185        let ptr = reg.get_ptr::<u32>().expect("u32 must be found");
186        assert_eq!(unsafe { *ptr }, 42);
187    }
188
189    #[test]
190    fn get_ptr_returns_none_for_unregistered() {
191        let reg = ResourceRegistry::new();
192        assert!(reg.get_ptr::<u64>().is_none());
193    }
194
195    #[test]
196    fn mutation_through_ptr() {
197        let mut reg = ResourceRegistry::new();
198        reg.insert(0u32);
199        let ptr = reg.get_ptr::<u32>().unwrap();
200        unsafe { *ptr = 99 };
201        let ptr2 = reg.get_ptr::<u32>().unwrap();
202        assert_eq!(unsafe { *ptr2 }, 99);
203    }
204
205    #[test]
206    fn multiple_types() {
207        let mut reg = ResourceRegistry::new();
208        reg.insert(1u8);
209        reg.insert(2u16);
210        reg.insert(3u32);
211        assert_eq!(unsafe { *reg.get_ptr::<u8>().unwrap() }, 1);
212        assert_eq!(unsafe { *reg.get_ptr::<u16>().unwrap() }, 2);
213        assert_eq!(unsafe { *reg.get_ptr::<u32>().unwrap() }, 3);
214    }
215
216    #[test]
217    fn alignment_respected() {
218        let mut reg = ResourceRegistry::new();
219        reg.insert(1u8);
220        reg.insert(2u64); // must be 8-byte aligned despite u8 before it
221        let ptr = reg.get_ptr::<u64>().unwrap();
222        assert_eq!(ptr as usize % mem::align_of::<u64>(), 0);
223        assert_eq!(unsafe { *ptr }, 2);
224    }
225
226    #[test]
227    #[should_panic(expected = "too many resources")]
228    fn panics_on_overflow() {
229        let mut reg = ResourceRegistry::new();
230        reg.insert(0u8);
231        reg.insert(0u16);
232        reg.insert(0u32);
233        reg.insert(0u64);
234        reg.insert(0i8);
235        reg.insert(0i16);
236        reg.insert(0i32);
237        reg.insert(0i64);
238        reg.insert(0f32); // 9th — should panic
239    }
240
241    #[test]
242    #[should_panic(expected = "duplicate")]
243    fn panics_on_duplicate_typeid() {
244        let mut reg = ResourceRegistry::new();
245        reg.insert(0u32);
246        reg.insert(1u32); // same TypeId — should panic
247    }
248
249    // ── Drop tests ────────────────────────────────────────────────────────────
250
251    // Each newtype is a distinct TypeId so all three can coexist in the registry.
252    // The Drop impl records the global sequence position into a per-type slot.
253    static DROP_SEQ: AtomicUsize = AtomicUsize::new(0);
254    static DROP_POS_A: AtomicUsize = AtomicUsize::new(0);
255    static DROP_POS_B: AtomicUsize = AtomicUsize::new(0);
256    static DROP_POS_C: AtomicUsize = AtomicUsize::new(0);
257
258    struct CounterA;
259    struct CounterB;
260    struct CounterC;
261
262    impl Drop for CounterA {
263        fn drop(&mut self) {
264            DROP_POS_A.store(
265                DROP_SEQ.fetch_add(1, Ordering::SeqCst) + 1,
266                Ordering::SeqCst,
267            );
268        }
269    }
270    impl Drop for CounterB {
271        fn drop(&mut self) {
272            DROP_POS_B.store(
273                DROP_SEQ.fetch_add(1, Ordering::SeqCst) + 1,
274                Ordering::SeqCst,
275            );
276        }
277    }
278    impl Drop for CounterC {
279        fn drop(&mut self) {
280            DROP_POS_C.store(
281                DROP_SEQ.fetch_add(1, Ordering::SeqCst) + 1,
282                Ordering::SeqCst,
283            );
284        }
285    }
286
287    #[test]
288    fn drops_in_reverse_insertion_order() {
289        // Reset shared atomics — tests may run in any order.
290        DROP_SEQ.store(0, Ordering::SeqCst);
291        DROP_POS_A.store(0, Ordering::SeqCst);
292        DROP_POS_B.store(0, Ordering::SeqCst);
293        DROP_POS_C.store(0, Ordering::SeqCst);
294
295        {
296            let mut reg = ResourceRegistry::new();
297            reg.insert(CounterA); // inserted 1st
298            reg.insert(CounterB); // inserted 2nd
299            reg.insert(CounterC); // inserted 3rd
300        } // reg dropped here → C first, then B, then A
301
302        // Sequence positions: C=1, B=2, A=3 (smaller = earlier in drop order)
303        let pos_a = DROP_POS_A.load(Ordering::SeqCst);
304        let pos_b = DROP_POS_B.load(Ordering::SeqCst);
305        let pos_c = DROP_POS_C.load(Ordering::SeqCst);
306        assert!(pos_c < pos_b, "C must be dropped before B");
307        assert!(pos_b < pos_a, "B must be dropped before A");
308    }
309
310    static DROP_RAN: AtomicUsize = AtomicUsize::new(0);
311
312    struct DropWitness;
313    impl Drop for DropWitness {
314        fn drop(&mut self) {
315            DROP_RAN.fetch_add(1, Ordering::SeqCst);
316        }
317    }
318
319    #[test]
320    fn drop_runs_for_single_entry() {
321        DROP_RAN.store(0, Ordering::SeqCst);
322        {
323            let mut reg = ResourceRegistry::new();
324            reg.insert(DropWitness);
325        }
326        assert_eq!(DROP_RAN.load(Ordering::SeqCst), 1);
327    }
328
329    #[test]
330    fn empty_registry_drop_is_noop() {
331        // Must not panic or exhibit UB.
332        drop(ResourceRegistry::new());
333    }
334
335    #[test]
336    #[should_panic(expected = "alignment")]
337    fn panics_on_overalign() {
338        #[repr(align(16))]
339        #[allow(dead_code)]
340        struct OverAligned(u64);
341
342        let mut reg = ResourceRegistry::new();
343        reg.insert(OverAligned(0));
344    }
345}