use std::alloc::{Layout, LayoutError};
use std::borrow::Borrow;
use std::cmp::Ordering;
use std::fmt::{self, 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::string::IString;
use crate::thin::{ThinMut, ThinMutExt, ThinRef, ThinRefExt};
use crate::value::{
string_cmp, string_debug, Destructured, DestructuredMut, DestructuredRef, IValue, ValueRepr,
ValueType,
};
#[repr(C)]
#[repr(align(8))]
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);
}
pub(crate) 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) -> NonNull<u8> {
unsafe {
self.ptr.as_ref().rc.fetch_add(1, AtomicOrdering::Relaxed);
}
self.ptr.cast::<u8>()
}
}
pub(crate) struct InternedRepr;
impl InternedRepr {
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
}
}
unsafe fn dealloc(ptr: NonNull<Header>) {
let hd = ThinRef::new(ptr);
let layout = Self::layout(hd.len()).unwrap();
dealloc_infallible(ptr.cast::<u8>(), layout);
}
pub(crate) fn intern(s: &str) -> NonNull<u8> {
debug_assert!(
s.len() > crate::value::inline::string::CAPACITY,
"a string that fits inline must not be interned: it would exist in two forms",
);
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
}
}
unsafe fn as_header<'a>(ptr: NonNull<u8>) -> ThinRef<'a, Header> {
ThinRef::new(ptr.cast())
}
unsafe fn bytes<'a>(ptr: NonNull<u8>) -> &'a [u8] {
Self::as_header(ptr).bytes()
}
unsafe fn bump_rc(ptr: NonNull<u8>) {
Self::as_header(ptr)
.rc
.fetch_add(1, AtomicOrdering::Relaxed);
}
unsafe fn release(ptr: NonNull<u8>) {
let hd = Self::as_header(ptr);
let mut rc = hd.rc.load(AtomicOrdering::Relaxed);
while rc > 1 {
match hd.rc.compare_exchange_weak(
rc,
rc - 1,
AtomicOrdering::Release,
AtomicOrdering::Relaxed,
) {
Ok(_) => return,
Err(new_rc) => rc = new_rc,
}
}
let cache = &*STRING_CACHE;
let shard = cache.shards().get_unchecked(hd.shard_index());
let mut guard = shard.write();
if hd.rc.fetch_sub(1, AtomicOrdering::Acquire) == 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(ptr.cast());
}
}
}
impl ValueRepr for InternedRepr {
fn value_type(&self, _v: &IValue) -> ValueType {
ValueType::String
}
unsafe fn partial_cmp(&self, a: &IValue, b: &IValue) -> Option<Ordering> {
Some(string_cmp(a, b))
}
unsafe fn debug(&self, v: &IValue, f: &mut Formatter<'_>) -> fmt::Result {
string_debug(v, f)
}
fn destructure(&self, v: IValue) -> Destructured {
Destructured::String(IString(v))
}
unsafe fn destructure_ref<'a>(&self, v: &'a IValue) -> DestructuredRef<'a> {
DestructuredRef::String(v.as_string_unchecked())
}
unsafe fn destructure_mut<'a>(&self, v: &'a mut IValue) -> DestructuredMut<'a> {
DestructuredMut::String(v.as_string_unchecked_mut())
}
unsafe fn clone(&self, v: &IValue) -> IValue {
Self::bump_rc(v.ptr());
v.raw_copy()
}
unsafe fn drop(&self, v: &mut IValue) {
Self::release(v.ptr());
}
unsafe fn as_bytes<'a>(&self, v: &'a IValue) -> Option<&'a [u8]> {
Some(Self::bytes(v.ptr()))
}
}