1use std::alloc::{alloc, dealloc, Layout, LayoutError};
4use std::borrow::Borrow;
5use std::cmp::Ordering;
6use std::fmt::{self, Debug, Display, Formatter};
7use std::hash::Hash;
8use std::ops::Deref;
9use std::ptr::{copy_nonoverlapping, NonNull};
10use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
11
12use dashmap::{DashSet, SharedValue};
13use lazy_static::lazy_static;
14
15use crate::thin::{ThinMut, ThinMutExt, ThinRef, ThinRefExt};
16
17use super::value::{IValue, TypeTag};
18
19#[repr(C)]
20#[repr(align(4))]
21struct Header {
22 rc: AtomicUsize,
23 len_lower: u32,
25 len_upper: u16,
26 shard_index: u16,
27}
28
29trait HeaderRef<'a>: ThinRefExt<'a, Header> {
30 fn len(&self) -> usize {
31 (u64::from(self.len_lower) | (u64::from(self.len_upper) << 32)) as usize
32 }
33 fn shard_index(&self) -> usize {
34 self.shard_index as usize
35 }
36 fn str_ptr(&self) -> *const u8 {
37 unsafe { self.ptr().add(1).cast() }
39 }
40 fn bytes(&self) -> &'a [u8] {
41 unsafe { std::slice::from_raw_parts(self.str_ptr(), self.len()) }
43 }
44 fn str(&self) -> &'a str {
45 unsafe { std::str::from_utf8_unchecked(self.bytes()) }
47 }
48}
49
50trait HeaderMut<'a>: ThinMutExt<'a, Header> {
51 fn str_ptr_mut(mut self) -> *mut u8 {
52 unsafe { self.ptr_mut().add(1).cast() }
54 }
55}
56
57impl<'a, T: ThinRefExt<'a, Header>> HeaderRef<'a> for T {}
58impl<'a, T: ThinMutExt<'a, Header>> HeaderMut<'a> for T {}
59
60lazy_static! {
61 static ref STRING_CACHE: DashSet<WeakIString> = DashSet::new();
62}
63
64#[cfg(any(test, feature = "ctor"))]
67#[ctor::ctor]
68fn ctor_init_cache() {
69 lazy_static::initialize(&STRING_CACHE);
70}
71
72#[doc(hidden)]
73pub fn init_cache() {
74 lazy_static::initialize(&STRING_CACHE);
75}
76
77struct WeakIString {
78 ptr: NonNull<Header>,
79}
80
81unsafe impl Send for WeakIString {}
82unsafe impl Sync for WeakIString {}
83impl PartialEq for WeakIString {
84 fn eq(&self, other: &Self) -> bool {
85 **self == **other
86 }
87}
88impl Eq for WeakIString {}
89impl Hash for WeakIString {
90 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
91 (**self).hash(state);
92 }
93}
94
95impl Deref for WeakIString {
96 type Target = str;
97 fn deref(&self) -> &str {
98 self.borrow()
99 }
100}
101
102impl Borrow<str> for WeakIString {
103 fn borrow(&self) -> &str {
104 self.header().str()
105 }
106}
107impl WeakIString {
108 fn header(&self) -> ThinRef<Header> {
109 unsafe { ThinRef::new(self.ptr.as_ptr()) }
111 }
112 fn upgrade(&self) -> IString {
113 unsafe {
114 self.ptr.as_ref().rc.fetch_add(1, AtomicOrdering::Relaxed);
115 IString(IValue::new_ptr(
116 self.ptr.as_ptr().cast::<u8>(),
117 TypeTag::StringOrNull,
118 ))
119 }
120 }
121}
122
123#[repr(transparent)]
138#[derive(Clone, size_of::SizeOf)]
139pub struct IString(pub(crate) IValue);
140
141impl Display for IString {
142 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
143 write!(f, "{}", self.as_str())
144 }
145}
146
147value_subtype_impls!(IString, into_string, as_string, as_string_mut);
148
149static EMPTY_HEADER: Header = Header {
150 len_lower: 0,
151 len_upper: 0,
152 shard_index: 0,
153 rc: AtomicUsize::new(0),
154};
155
156impl IString {
157 fn layout(len: usize) -> Result<Layout, LayoutError> {
158 Ok(Layout::new::<Header>()
159 .extend(Layout::array::<u8>(len)?)?
160 .0
161 .pad_to_align())
162 }
163
164 fn alloc(s: &str, shard_index: usize) -> *mut Header {
165 assert!((s.len() as u64) < (1 << 48));
166 assert!(shard_index < (1 << 16));
167 unsafe {
168 let ptr = alloc(Self::layout(s.len()).unwrap()).cast::<Header>();
169 ptr.write(Header {
170 len_lower: s.len() as u32,
171 len_upper: ((s.len() as u64) >> 32) as u16,
172 shard_index: shard_index as u16,
173 rc: AtomicUsize::new(0),
174 });
175 let hd = ThinMut::new(ptr);
176 copy_nonoverlapping(s.as_ptr(), hd.str_ptr_mut(), s.len());
177 ptr
178 }
179 }
180
181 fn dealloc(ptr: *mut Header) {
182 unsafe {
183 let hd = ThinRef::new(ptr);
184 let layout = Self::layout(hd.len()).unwrap();
185 dealloc(ptr.cast::<u8>(), layout);
186 }
187 }
188
189 #[must_use]
191 pub fn intern(s: &str) -> Self {
192 if s.is_empty() {
193 return Self::new();
194 }
195 let cache = &*STRING_CACHE;
196 let shard_index = cache.determine_map(s);
197
198 let shard = unsafe { cache.shards().get_unchecked(shard_index) };
200 let mut guard = shard.write();
201 if let Some((k, _)) = guard.get_key_value(s) {
202 k.upgrade()
203 } else {
204 let k = unsafe {
205 WeakIString {
206 ptr: NonNull::new_unchecked(Self::alloc(s, shard_index)),
207 }
208 };
209 let res = k.upgrade();
210 guard.insert(k, SharedValue::new(()));
211 res
212 }
213 }
214
215 fn header(&self) -> ThinRef<Header> {
216 unsafe { ThinRef::new(self.0.ptr().cast()) }
217 }
218
219 #[must_use]
221 pub fn len(&self) -> usize {
222 self.header().len()
223 }
224
225 #[must_use]
227 pub fn is_empty(&self) -> bool {
228 self.len() == 0
229 }
230
231 #[must_use]
233 pub fn as_str(&self) -> &str {
234 self.header().str()
235 }
236
237 #[must_use]
239 pub fn as_bytes(&self) -> &[u8] {
240 self.header().bytes()
241 }
242
243 #[must_use]
245 pub fn new() -> Self {
246 unsafe { IString(IValue::new_ref(&EMPTY_HEADER, TypeTag::StringOrNull)) }
247 }
248
249 pub(crate) fn clone_impl(&self) -> IValue {
250 if self.is_empty() {
251 Self::new().0
252 } else {
253 self.header().rc.fetch_add(1, AtomicOrdering::Relaxed);
254 unsafe { self.0.raw_copy() }
255 }
256 }
257 pub(crate) fn drop_impl(&mut self) {
258 if !self.is_empty() {
259 let hd = self.header();
260
261 let mut rc = hd.rc.load(AtomicOrdering::Relaxed);
264 while rc > 1 {
265 match hd.rc.compare_exchange_weak(
266 rc,
267 rc - 1,
268 AtomicOrdering::Relaxed,
269 AtomicOrdering::Relaxed,
270 ) {
271 Ok(_) => return,
272 Err(new_rc) => rc = new_rc,
273 }
274 }
275
276 let cache = &*STRING_CACHE;
278 let shard = unsafe { cache.shards().get_unchecked(hd.shard_index()) };
280 let mut guard = shard.write();
281 if hd.rc.fetch_sub(1, AtomicOrdering::Relaxed) == 1 {
282 assert!(guard.remove(hd.str()).is_some());
284
285 if guard.len() * 3 < guard.capacity() || guard.is_empty() {
290 guard.shrink_to_fit();
291 }
292 drop(guard);
293
294 Self::dealloc(unsafe { self.0.ptr().cast() });
295 }
296 }
297 }
298}
299
300impl Deref for IString {
301 type Target = str;
302 fn deref(&self) -> &str {
303 self.as_str()
304 }
305}
306
307impl Borrow<str> for IString {
308 fn borrow(&self) -> &str {
309 self.as_str()
310 }
311}
312
313impl From<&str> for IString {
314 fn from(other: &str) -> Self {
315 Self::intern(other)
316 }
317}
318
319impl From<&mut str> for IString {
320 fn from(other: &mut str) -> Self {
321 Self::intern(other)
322 }
323}
324
325impl From<String> for IString {
326 fn from(other: String) -> Self {
327 Self::intern(other.as_str())
328 }
329}
330
331impl From<&String> for IString {
332 fn from(other: &String) -> Self {
333 Self::intern(other.as_str())
334 }
335}
336
337impl From<&mut String> for IString {
338 fn from(other: &mut String) -> Self {
339 Self::intern(other.as_str())
340 }
341}
342
343impl From<IString> for String {
344 fn from(other: IString) -> Self {
345 other.as_str().into()
346 }
347}
348
349impl PartialEq for IString {
350 fn eq(&self, other: &Self) -> bool {
351 self.0.raw_eq(&other.0)
352 }
353}
354
355impl PartialEq<str> for IString {
356 fn eq(&self, other: &str) -> bool {
357 self.as_str() == other
358 }
359}
360
361impl PartialEq<IString> for str {
362 fn eq(&self, other: &IString) -> bool {
363 self == other.as_str()
364 }
365}
366
367impl PartialEq<String> for IString {
368 fn eq(&self, other: &String) -> bool {
369 self.as_str() == other
370 }
371}
372
373impl PartialEq<IString> for String {
374 fn eq(&self, other: &IString) -> bool {
375 self == other.as_str()
376 }
377}
378
379impl Default for IString {
380 fn default() -> Self {
381 Self::new()
382 }
383}
384
385impl Eq for IString {}
386impl Ord for IString {
387 fn cmp(&self, other: &Self) -> Ordering {
388 if self == other {
389 Ordering::Equal
390 } else {
391 self.as_str().cmp(other.as_str())
392 }
393 }
394}
395impl PartialOrd for IString {
396 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
397 Some(self.cmp(other))
398 }
399}
400impl Hash for IString {
401 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
402 self.0.raw_hash(state);
403 }
404}
405
406impl Debug for IString {
407 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
408 Debug::fmt(self.as_str(), f)
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415
416 #[mockalloc::test]
417 fn can_intern() {
418 let x = IString::intern("foo");
419 let y = IString::intern("bar");
420 let z = IString::intern("foo");
421
422 assert_eq!(x.as_ptr(), z.as_ptr());
423 assert_ne!(x.as_ptr(), y.as_ptr());
424 assert_eq!(x.as_str(), "foo");
425 assert_eq!(y.as_str(), "bar");
426 }
427
428 #[mockalloc::test]
429 fn default_interns_string() {
430 let x = IString::intern("");
431 let y = IString::new();
432 let z = IString::intern("foo");
433
434 assert_eq!(x.as_ptr(), y.as_ptr());
435 assert_ne!(x.as_ptr(), z.as_ptr());
436 }
437}