stringleton_registry/
registry.rs1use core::{borrow::Borrow, hash::Hash};
2
3use crate::{Site, Symbol};
4use hashbrown::{HashMap, hash_map};
5
6#[cfg(feature = "alloc")]
7use alloc::{borrow::ToOwned, boxed::Box};
8
9#[cfg(not(any(feature = "std", feature = "critical-section")))]
10compile_error!("Either the `std` or `critical-section` feature must be enabled");
11#[cfg(not(any(feature = "std", feature = "spin")))]
12compile_error!("Either the `std` or `spin` feature must be enabled");
13
14#[cfg(feature = "spin")]
15use spin::{RwLock, RwLockReadGuard, RwLockWriteGuard};
16#[cfg(not(feature = "spin"))]
17use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
18
19#[cfg(feature = "critical-section")]
20use once_cell::sync::OnceCell as OnceLock;
21#[cfg(not(feature = "critical-section"))]
22use std::sync::OnceLock;
23
24#[derive(Clone, Copy, PartialEq, Eq)]
26struct SymbolStr(&'static &'static str);
27impl SymbolStr {
28 #[inline]
29 fn address(&self) -> usize {
30 core::ptr::from_ref::<&'static str>(self.0) as usize
31 }
32}
33impl Borrow<str> for SymbolStr {
34 #[inline]
35 fn borrow(&self) -> &str {
36 self.0
37 }
38}
39impl Hash for SymbolStr {
40 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
41 (*self.0).hash(state);
42 }
43}
44
45#[cfg(feature = "alloc")]
46impl From<&str> for SymbolStr {
47 #[inline]
48 fn from(value: &str) -> Self {
49 let value = &*Box::leak(Box::new(&*value.to_owned().leak()));
50 Self(value)
51 }
52}
53
54pub struct Registry {
59 #[cfg(not(feature = "spin"))]
60 store: std::sync::RwLock<Store>,
61 #[cfg(feature = "spin")]
62 store: spin::RwLock<Store>,
63}
64
65#[derive(Default)]
66pub(crate) struct Store {
67 by_string: HashMap<SymbolStr, ()>,
68 by_pointer: HashMap<usize, SymbolStr>,
69}
70
71pub struct RegistryReadGuard {
73 guard: RwLockReadGuard<'static, Store>,
75}
76
77pub struct RegistryWriteGuard {
79 guard: RwLockWriteGuard<'static, Store>,
81}
82
83impl Registry {
84 #[inline]
85 fn new() -> Self {
86 Self {
87 store: RwLock::default(),
88 }
89 }
90
91 pub fn global() -> &'static Registry {
93 static REGISTRY: OnceLock<Registry> = OnceLock::new();
94 REGISTRY.get_or_init(Registry::new)
95 }
96
97 #[inline]
103 pub fn read(&'static self) -> RegistryReadGuard {
104 RegistryReadGuard {
105 #[cfg(not(feature = "spin"))]
106 guard: self
107 .store
108 .read()
109 .unwrap_or_else(std::sync::PoisonError::into_inner),
110 #[cfg(feature = "spin")]
111 guard: self.store.read(),
112 }
113 }
114
115 #[inline]
120 pub fn write(&'static self) -> RegistryWriteGuard {
121 RegistryWriteGuard {
122 #[cfg(not(feature = "spin"))]
123 guard: self
124 .store
125 .write()
126 .unwrap_or_else(std::sync::PoisonError::into_inner),
127 #[cfg(feature = "spin")]
128 guard: self.store.write(),
129 }
130 }
131
132 pub unsafe fn register_sites(table: &[Site]) {
145 unsafe {
146 Registry::global().write().register_sites(table);
147 }
148 }
149
150 #[must_use]
153 #[inline]
154 pub fn get(&'static self, string: &str) -> Option<Symbol> {
155 self.read().guard.get(string)
156 }
157
158 #[cfg(any(feature = "std", feature = "alloc"))]
167 #[must_use]
168 pub fn get_or_insert(&'static self, string: &str) -> Symbol {
169 let read = self.read();
170 if let Some(previously_interned) = read.get(string) {
171 return previously_interned;
172 }
173 core::mem::drop(read);
174 let mut write = self.write();
175 write.get_or_insert(string)
176 }
177
178 #[inline]
193 #[must_use]
194 pub fn get_or_insert_static(&'static self, string: &'static &'static str) -> Symbol {
195 let read = self.read();
196 if let Some(previously_interned) = read.get(string) {
197 return previously_interned;
198 }
199 core::mem::drop(read);
200
201 let mut write = self.write();
202 write.get_or_insert_static(string)
203 }
204
205 #[inline]
211 #[must_use]
212 pub fn get_by_address(&'static self, address: u64) -> Option<Symbol> {
213 self.read().get_by_address(address)
214 }
215}
216
217impl Store {
218 #[cfg(any(feature = "std", feature = "alloc"))]
219 pub fn get_or_insert(&mut self, string: &str) -> Symbol {
220 let entry;
221 match self.by_string.entry_ref(string) {
222 hash_map::EntryRef::Occupied(e) => entry = e,
223 hash_map::EntryRef::Vacant(e) => {
224 entry = e.insert_entry(());
226 let interned = entry.key();
227 self.by_pointer.insert(interned.address(), *interned);
228 }
229 }
230
231 unsafe {
232 Symbol::new_unchecked(entry.key().0)
234 }
235 }
236
237 pub fn get_or_insert_static(&mut self, string: &'static &'static str) -> Symbol {
240 let symstr = SymbolStr(string);
243
244 let interned = match self.by_string.entry(symstr) {
245 hash_map::Entry::Occupied(entry) => *entry.key(), hash_map::Entry::Vacant(entry) => {
247 let key = *entry.insert_entry(()).key();
248 self.by_pointer.insert(key.address(), key);
249 key
250 }
251 };
252
253 unsafe {
254 Symbol::new_unchecked(interned.0)
256 }
257 }
258
259 pub fn get(&self, string: &str) -> Option<Symbol> {
260 self.by_string
261 .get_key_value(string)
262 .map(|(symstr, ())| unsafe {
263 Symbol::new_unchecked(symstr.0)
265 })
266 }
267
268 #[allow(clippy::cast_possible_truncation)] pub fn get_by_address(&self, address: u64) -> Option<Symbol> {
270 self.by_pointer
271 .get(&(address as usize))
272 .map(|symstr| unsafe {
273 Symbol::new_unchecked(symstr.0)
275 })
276 }
277}
278
279impl RegistryReadGuard {
280 #[inline]
282 #[must_use]
283 pub fn len(&self) -> usize {
284 self.guard.by_string.len()
285 }
286
287 #[inline]
289 #[must_use]
290 pub fn is_empty(&self) -> bool {
291 self.guard.by_string.is_empty()
292 }
293
294 #[inline]
299 #[must_use]
300 pub fn get(&self, string: &str) -> Option<Symbol> {
301 self.guard.get(string)
302 }
303
304 #[inline]
310 #[must_use]
311 pub fn get_by_address(&self, address: u64) -> Option<Symbol> {
312 self.guard.get_by_address(address)
313 }
314}
315
316impl RegistryWriteGuard {
317 unsafe fn register_sites(&mut self, sites: &[Site]) {
318 unsafe {
319 for registration in sites {
320 let string = registration.get_string();
321 let interned = self.guard.get_or_insert_static(string);
322 registration.initialize(interned);
325 }
326 }
327 }
328
329 #[inline]
331 #[must_use]
332 pub fn len(&self) -> usize {
333 self.guard.by_string.len()
334 }
335
336 #[inline]
338 #[must_use]
339 pub fn is_empty(&self) -> bool {
340 self.guard.by_string.is_empty()
341 }
342
343 #[inline]
344 #[must_use]
345 pub fn get(&self, string: &str) -> Option<Symbol> {
346 self.guard.get(string)
347 }
348
349 #[inline]
355 #[must_use]
356 pub fn get_by_address(&self, address: u64) -> Option<Symbol> {
357 self.guard.get_by_address(address)
358 }
359
360 #[inline]
362 #[must_use]
363 #[cfg(feature = "alloc")]
364 pub fn get_or_insert(&mut self, string: &str) -> Symbol {
365 self.guard.get_or_insert(string)
366 }
367
368 #[inline]
376 #[must_use]
377 pub fn get_or_insert_static(&mut self, string: &'static &'static str) -> Symbol {
378 self.guard.get_or_insert_static(string)
379 }
380}