1use std::{
2 borrow::Cow,
3 cell::RefCell,
4 ffi::c_void,
5 hash::{BuildHasherDefault, Hash, Hasher},
6 mem::ManuallyDrop,
7 num::NonZeroU8,
8 ops::Deref,
9 ptr::NonNull,
10};
11
12use rustc_hash::FxHasher;
13use triomphe::ThinArc;
14
15use crate::{
16 tagged_value::{TaggedValue, MAX_INLINE_LEN},
17 wtf8::Wtf8,
18 Atom, Wtf8Atom, INLINE_TAG, INLINE_TAG_INIT, LEN_OFFSET, TAG_MASK,
19};
20
21#[derive(PartialEq, Eq)]
22pub(crate) struct Metadata {
23 pub hash: u64,
24}
25
26#[derive(Clone, PartialEq, Eq)]
27pub(crate) struct Item(pub ThinArc<Metadata, u8>);
28
29impl Deref for Item {
30 type Target = <ThinArc<Metadata, u8> as Deref>::Target;
31
32 fn deref(&self) -> &Self::Target {
33 &self.0
34 }
35}
36
37impl Hash for Item {
38 fn hash<H: Hasher>(&self, state: &mut H) {
39 state.write_u64(self.0.header.header.hash);
40 }
41}
42
43pub(crate) unsafe fn deref_from(ptr: TaggedValue) -> ManuallyDrop<Item> {
44 let item = restore_arc(ptr);
45
46 ManuallyDrop::new(item)
47}
48
49pub(crate) unsafe fn restore_arc(v: TaggedValue) -> Item {
50 let ptr = v.get_ptr();
51 Item(ThinArc::from_raw(ptr))
52}
53
54pub struct AtomStore {
60 pub(crate) data: hashbrown::HashMap<Item, (), BuildEntryHasher>,
61}
62
63impl Default for AtomStore {
64 fn default() -> Self {
65 Self {
66 data: hashbrown::HashMap::with_capacity_and_hasher(64, BuildEntryHasher::default()),
67 }
68 }
69}
70
71impl AtomStore {
72 #[inline(always)]
73 pub fn atom<'a>(&mut self, text: impl Into<Cow<'a, str>>) -> Atom {
74 atom_in(self, &text.into())
75 }
76
77 #[inline(always)]
78 pub fn wtf8_atom<'a>(&mut self, text: impl Into<Cow<'a, Wtf8>>) -> Wtf8Atom {
79 wtf8_atom_in(self, text.into().as_bytes())
80 }
81
82 fn gc(&mut self) {
83 self.data.retain(|item, _| {
84 let count = ThinArc::strong_count(&item.0);
85 debug_assert!(count > 0);
86 count > 1
87 });
88 }
89}
90
91thread_local! {
92 static GLOBAL_DATA: RefCell<AtomStore> = Default::default();
93}
94
95pub fn global_atom_store_gc() {
97 GLOBAL_DATA.with(|global| {
98 let mut store = global.borrow_mut();
99 store.gc();
100 });
101}
102
103pub(crate) fn global_wtf8_atom(text: &[u8]) -> Wtf8Atom {
104 GLOBAL_DATA.with(|global| {
105 let mut store = global.borrow_mut();
106
107 wtf8_atom_in(&mut *store, text)
108 })
109}
110
111pub(crate) fn global_atom(text: &str) -> Atom {
112 GLOBAL_DATA.with(|global| {
113 let mut store = global.borrow_mut();
114
115 atom_in(&mut *store, text)
116 })
117}
118
119fn wtf8_atom_in<S>(storage: S, text: &[u8]) -> Wtf8Atom
120where
121 S: Storage,
122{
123 let len = text.len();
124
125 if len <= MAX_INLINE_LEN {
126 let tag = INLINE_TAG_INIT | ((len as u8) << LEN_OFFSET);
128 let mut unsafe_data = TaggedValue::new_tag(tag);
129 unsafe {
130 unsafe_data.data_mut()[..len].copy_from_slice(text);
131 }
132 return Wtf8Atom { unsafe_data };
133 }
134
135 let hash = calc_hash(text);
136 let entry = storage.insert_entry(text, hash);
137 let entry = ThinArc::into_raw(entry.0) as *mut c_void;
138
139 let ptr: NonNull<c_void> = unsafe {
140 NonNull::new_unchecked(entry)
142 };
143 debug_assert!(0 == ptr.as_ptr() as u8 & TAG_MASK);
144 Wtf8Atom {
145 unsafe_data: TaggedValue::new_ptr(ptr),
146 }
147}
148
149fn atom_in<S>(storage: S, text: &str) -> Atom
152where
153 S: Storage,
154{
155 unsafe { Atom::from_wtf8_unchecked(wtf8_atom_in(storage, text.as_bytes())) }
157}
158
159pub(crate) const fn inline_atom(text: &str) -> Option<Atom> {
162 let len = text.len();
163 if len <= MAX_INLINE_LEN {
164 let tag = INLINE_TAG | ((len as u8) << LEN_OFFSET);
166 let mut unsafe_data = TaggedValue::new_tag(match NonZeroU8::new(tag) {
167 Some(tag) => tag,
168 None => unreachable!(),
169 });
170 unsafe {
173 let data = unsafe_data.data_mut();
174 let bytes = text.as_bytes();
175 let mut i = 0;
176 while i < len {
177 data[i] = bytes[i];
178 i += 1;
179 }
180 }
181 return Some(Atom { unsafe_data });
182 }
183 None
184}
185
186trait Storage {
187 fn insert_entry(self, text: &[u8], hash: u64) -> Item;
188}
189
190impl Storage for &'_ mut AtomStore {
191 fn insert_entry(self, text: &[u8], hash: u64) -> Item {
192 if text.len() > 512 {
194 return Item(ThinArc::from_header_and_slice(Metadata { hash }, text));
195 }
196
197 let (entry, _) = self
198 .data
199 .raw_entry_mut()
200 .from_hash(hash, |key| {
201 key.header.header.hash == hash && key.slice.eq(text)
202 })
203 .or_insert_with(move || {
204 (
205 Item(ThinArc::from_header_and_slice(Metadata { hash }, text)),
206 (),
207 )
208 });
209 entry.clone()
210 }
211}
212
213#[inline(always)]
214fn calc_hash(text: &[u8]) -> u64 {
215 let mut hasher = FxHasher::default();
216 text.hash(&mut hasher);
217 hasher.finish()
218}
219
220type BuildEntryHasher = BuildHasherDefault<EntryHasher>;
221
222#[derive(Default)]
227pub(crate) struct EntryHasher {
228 hash: u64,
229 #[cfg(debug_assertions)]
230 write_called: bool,
231}
232
233impl Hasher for EntryHasher {
234 fn finish(&self) -> u64 {
235 #[cfg(debug_assertions)]
236 debug_assert!(
237 self.write_called,
238 "EntryHasher expects write_u64 to have been called",
239 );
240 self.hash
241 }
242
243 fn write(&mut self, _bytes: &[u8]) {
244 panic!("EntryHasher expects to be called with write_u64");
245 }
246
247 fn write_u64(&mut self, val: u64) {
248 #[cfg(debug_assertions)]
249 {
250 debug_assert!(
251 !self.write_called,
252 "EntryHasher expects write_u64 to be called only once",
253 );
254 self.write_called = true;
255 }
256
257 self.hash = val;
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use crate::{dynamic::GLOBAL_DATA, global_atom_store_gc, Atom};
264
265 fn expect_size(expected: usize) {
266 GLOBAL_DATA.with(|global| {
268 let store = global.borrow();
269 assert_eq!(store.data.len(), expected);
270 })
271 }
272
273 #[test]
274 fn global_ref_count_dynamic_0() {
275 expect_size(0);
276
277 let atom1 = Atom::new("Hello, beautiful world!");
280
281 expect_size(1);
282
283 let atom2 = Atom::new("Hello, beautiful world!");
284
285 expect_size(1);
286
287 assert_eq!(atom1.ref_count(), 3);
289 assert_eq!(atom2.ref_count(), 3);
290
291 drop(atom1);
292
293 expect_size(1);
294
295 assert_eq!(atom2.ref_count(), 2);
297
298 drop(atom2);
299
300 expect_size(1);
301 global_atom_store_gc();
302 expect_size(0);
303 }
304
305 #[test]
306 fn global_ref_count_dynamic_1() {
307 expect_size(0);
308
309 {
310 expect_size(0);
311 let atom = Atom::new("Hello, beautiful world!");
312 assert_eq!(atom.ref_count(), 2);
313 expect_size(1);
314 }
315
316 expect_size(1);
317 global_atom_store_gc();
318 expect_size(0);
319
320 {
321 let atom = Atom::new("Hello, beautiful world!");
322 assert_eq!(atom.ref_count(), 2);
323 expect_size(1);
324 }
325 let atom = Atom::new("Hello, beautiful world!");
326 assert_eq!(atom.ref_count(), 2);
327
328 expect_size(1);
329 global_atom_store_gc();
330 expect_size(1);
331
332 drop(atom);
333 expect_size(1);
334 global_atom_store_gc();
335 expect_size(0);
336 }
337}