use std::alloc::{Layout, LayoutError};
use std::borrow::Borrow;
use std::cmp::Ordering;
use std::fmt::{self, Debug, Display, Formatter};
use std::hash::Hash;
use std::ops::Deref;
use std::ptr::{copy_nonoverlapping, NonNull};
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
use dashmap::{DashSet, SharedValue};
use lazy_static::lazy_static;
use crate::alloc::{alloc_infallible, dealloc_infallible};
use crate::thin::{ThinMut, ThinMutExt, ThinRef, ThinRefExt};
use super::value::{IValue, TypeTag};
#[repr(C)]
#[repr(align(4))]
struct Header {
rc: AtomicUsize,
len_lower: u32,
len_upper: u16,
shard_index: u16,
}
trait HeaderRef<'a>: ThinRefExt<'a, Header> {
fn len(&self) -> usize {
(u64::from(self.len_lower) | (u64::from(self.len_upper) << 32)) as usize
}
fn shard_index(&self) -> usize {
self.shard_index as usize
}
fn str_ptr(&self) -> *const u8 {
unsafe { self.ptr().add(1).cast() }
}
fn bytes(&self) -> &'a [u8] {
unsafe { std::slice::from_raw_parts(self.str_ptr(), self.len()) }
}
fn str(&self) -> &'a str {
unsafe { std::str::from_utf8_unchecked(self.bytes()) }
}
}
trait HeaderMut<'a>: ThinMutExt<'a, Header> {
fn str_ptr_mut(mut self) -> *mut u8 {
unsafe { self.ptr_mut().add(1).cast() }
}
}
impl<'a, T: ThinRefExt<'a, Header>> HeaderRef<'a> for T {}
impl<'a, T: ThinMutExt<'a, Header>> HeaderMut<'a> for T {}
lazy_static! {
static ref STRING_CACHE: DashSet<WeakIString> = DashSet::new();
}
#[cfg(any(test, feature = "ctor"))]
#[ctor::ctor]
fn ctor_init_cache() {
lazy_static::initialize(&STRING_CACHE);
}
#[doc(hidden)]
pub fn init_cache() {
lazy_static::initialize(&STRING_CACHE);
}
struct WeakIString {
ptr: NonNull<Header>,
}
unsafe impl Send for WeakIString {}
unsafe impl Sync for WeakIString {}
impl PartialEq for WeakIString {
fn eq(&self, other: &Self) -> bool {
**self == **other
}
}
impl Eq for WeakIString {}
impl Hash for WeakIString {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
(**self).hash(state);
}
}
impl Deref for WeakIString {
type Target = str;
fn deref(&self) -> &str {
self.borrow()
}
}
impl Borrow<str> for WeakIString {
fn borrow(&self) -> &str {
self.header().str()
}
}
impl WeakIString {
fn header<'a>(&'a self) -> ThinRef<'a, Header> {
unsafe { ThinRef::new(self.ptr) }
}
fn upgrade(&self) -> IString {
unsafe {
self.ptr.as_ref().rc.fetch_add(1, AtomicOrdering::Relaxed);
IString(IValue::new_ptr(
self.ptr.cast::<u8>(),
TypeTag::StringOrNull,
))
}
}
}
#[repr(transparent)]
#[derive(Clone)]
pub struct IString(pub(crate) IValue);
value_subtype_impls!(IString, into_string, as_string, as_string_mut);
static EMPTY_HEADER: Header = Header {
len_lower: 0,
len_upper: 0,
shard_index: 0,
rc: AtomicUsize::new(0),
};
impl IString {
fn layout(len: usize) -> Result<Layout, LayoutError> {
Ok(Layout::new::<Header>()
.extend(Layout::array::<u8>(len)?)?
.0
.pad_to_align())
}
fn alloc(s: &str, shard_index: usize) -> NonNull<Header> {
assert!((s.len() as u64) < (1 << 48));
assert!(shard_index < (1 << 16));
unsafe {
let ptr = alloc_infallible(Self::layout(s.len()).unwrap()).cast::<Header>();
ptr.write(Header {
len_lower: s.len() as u32,
len_upper: ((s.len() as u64) >> 32) as u16,
shard_index: shard_index as u16,
rc: AtomicUsize::new(0),
});
let hd = ThinMut::new(ptr);
copy_nonoverlapping(s.as_ptr(), hd.str_ptr_mut(), s.len());
ptr
}
}
fn dealloc(ptr: NonNull<Header>) {
unsafe {
let hd = ThinRef::new(ptr);
let layout = Self::layout(hd.len()).unwrap();
dealloc_infallible(ptr.cast::<u8>(), layout);
}
}
#[must_use]
pub fn intern(s: &str) -> Self {
if s.is_empty() {
return Self::new();
}
let cache = &*STRING_CACHE;
let shard_index = cache.determine_map(s);
let shard = unsafe { cache.shards().get_unchecked(shard_index) };
let mut guard = shard.write();
if let Some((k, _)) = guard.get_key_value(s) {
k.upgrade()
} else {
let k = WeakIString {
ptr: Self::alloc(s, shard_index),
};
let res = k.upgrade();
guard.insert(k, SharedValue::new(()));
res
}
}
fn header<'a>(&'a self) -> ThinRef<'a, Header> {
unsafe { ThinRef::new(self.0.ptr().cast()) }
}
#[must_use]
pub fn len(&self) -> usize {
self.header().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn as_str(&self) -> &str {
self.header().str()
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
self.header().bytes()
}
#[must_use]
pub fn new() -> Self {
unsafe { IString(IValue::new_ref(&EMPTY_HEADER, TypeTag::StringOrNull)) }
}
pub(crate) fn clone_impl(&self) -> IValue {
if self.is_empty() {
Self::new().0
} else {
self.header().rc.fetch_add(1, AtomicOrdering::Relaxed);
unsafe { self.0.raw_copy() }
}
}
pub(crate) fn drop_impl(&mut self) {
if !self.is_empty() {
let hd = self.header();
let mut rc = hd.rc.load(AtomicOrdering::Relaxed);
while rc > 1 {
match hd.rc.compare_exchange_weak(
rc,
rc - 1,
AtomicOrdering::Relaxed,
AtomicOrdering::Relaxed,
) {
Ok(_) => return,
Err(new_rc) => rc = new_rc,
}
}
let cache = &*STRING_CACHE;
let shard = unsafe { cache.shards().get_unchecked(hd.shard_index()) };
let mut guard = shard.write();
if hd.rc.fetch_sub(1, AtomicOrdering::Relaxed) == 1 {
assert!(guard.remove(hd.str()).is_some());
if guard.len() * 3 < guard.capacity() || guard.is_empty() {
guard.shrink_to_fit();
}
drop(guard);
Self::dealloc(unsafe { self.0.ptr().cast() });
}
}
}
}
impl Deref for IString {
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
#[cfg(feature = "broken-borrow-impl-compat")]
impl Borrow<str> for IString {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl From<&str> for IString {
fn from(other: &str) -> Self {
Self::intern(other)
}
}
impl From<&mut str> for IString {
fn from(other: &mut str) -> Self {
Self::intern(other)
}
}
impl From<String> for IString {
fn from(other: String) -> Self {
Self::intern(other.as_str())
}
}
impl From<&String> for IString {
fn from(other: &String) -> Self {
Self::intern(other.as_str())
}
}
impl From<&mut String> for IString {
fn from(other: &mut String) -> Self {
Self::intern(other.as_str())
}
}
impl From<IString> for String {
fn from(other: IString) -> Self {
other.as_str().into()
}
}
impl PartialEq for IString {
fn eq(&self, other: &Self) -> bool {
self.0.raw_eq(&other.0)
}
}
impl PartialEq<str> for IString {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<IString> for str {
fn eq(&self, other: &IString) -> bool {
self == other.as_str()
}
}
impl PartialEq<String> for IString {
fn eq(&self, other: &String) -> bool {
self.as_str() == other
}
}
impl PartialEq<IString> for String {
fn eq(&self, other: &IString) -> bool {
self == other.as_str()
}
}
impl Default for IString {
fn default() -> Self {
Self::new()
}
}
impl Eq for IString {}
impl Ord for IString {
fn cmp(&self, other: &Self) -> Ordering {
if self == other {
Ordering::Equal
} else {
self.as_str().cmp(other.as_str())
}
}
}
impl PartialOrd for IString {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Hash for IString {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.raw_hash(state);
}
}
impl Debug for IString {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Debug::fmt(self.as_str(), f)
}
}
impl Display for IString {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(self.as_str(), f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[mockalloc::test]
fn can_intern() {
let x = IString::intern("foo");
let y = IString::intern("bar");
let z = IString::intern("foo");
assert_eq!(x.as_ptr(), z.as_ptr());
assert_ne!(x.as_ptr(), y.as_ptr());
assert_eq!(x.as_str(), "foo");
assert_eq!(y.as_str(), "bar");
}
#[mockalloc::test]
fn default_interns_string() {
let x = IString::intern("");
let y = IString::new();
let z = IString::intern("foo");
assert_eq!(x.as_ptr(), y.as_ptr());
assert_ne!(x.as_ptr(), z.as_ptr());
}
}