use crate::{InternerSymbol, Symbol};
use bumpalo::Bump;
use hashbrown::hash_table;
use std::{cell::UnsafeCell, collections::hash_map::RandomState, hash::BuildHasher};
pub(crate) type Map<S> = hash_table::HashTable<RawMapKey<S>>;
pub(crate) type MapKey = (u64, &'static [u8]);
pub(crate) type RawMapKey<S> = (MapKey, S);
pub struct BytesInterner<S = Symbol, H = RandomState> {
pub(crate) map: UnsafeCell<Map<S>>,
hash_builder: H,
strs: UnsafeCell<Vec<&'static [u8]>>,
arena: Bump,
}
impl_interner_api!(
BytesInterner,
value = [u8],
mut_note = "this never uses shared interior mutability.",
cheap_op = "cheap operation"
);
impl<S: InternerSymbol, H: BuildHasher> BytesInterner<S, H> {
fn with_capacity_and_hasher_impl(capacity: usize, hash_builder: H) -> Self {
let map = Map::with_capacity(capacity);
let strs = Vec::with_capacity(capacity);
Self {
map: UnsafeCell::new(map),
hash_builder,
strs: UnsafeCell::new(strs),
arena: Bump::new(),
}
}
#[inline]
fn len_impl(&self) -> usize {
unsafe { (*self.strs.get()).len() }
}
#[inline]
fn intern_impl(&self, s: &[u8]) -> S {
self.do_intern(s, alloc)
}
#[inline]
fn intern_mut_impl(&mut self, s: &[u8]) -> S {
self.do_intern_mut(s, alloc)
}
#[inline]
fn intern_static_impl(&self, s: &'static [u8]) -> S {
self.do_intern(s, no_alloc)
}
#[inline]
unsafe fn intern_static_unchecked_impl(&self, s: &[u8]) -> S {
self.do_intern(s, no_alloc_unchecked)
}
#[inline]
fn intern_mut_static_impl(&mut self, s: &'static [u8]) -> S {
self.do_intern_mut(s, no_alloc)
}
#[inline]
unsafe fn intern_mut_static_unchecked_impl(&mut self, s: &[u8]) -> S {
self.do_intern_mut(s, no_alloc_unchecked)
}
#[inline]
#[cfg_attr(debug_assertions, track_caller)]
fn resolve_impl(&self, sym: S) -> &[u8] {
let strs = unsafe { &*self.strs.get() };
if cfg!(debug_assertions) {
strs.get(sym.to_usize()).copied().expect("symbol out of bounds")
} else {
unsafe { strs.get_unchecked(sym.to_usize()) }
}
}
#[inline]
fn try_resolve_impl(&self, sym: S) -> Option<&[u8]> {
let strs = unsafe { &*self.strs.get() };
strs.get(sym.to_usize()).copied()
}
#[inline]
fn do_intern<'a>(
&self,
s: &'a [u8],
alloc: impl FnOnce(&Bump, &'a [u8]) -> &'static [u8],
) -> S {
let hash = self.hash(s);
let (map, strs) = unsafe { (&mut *self.map.get(), &mut *self.strs.get()) };
get_or_insert(strs, &self.arena, s, hash, map, alloc)
}
#[inline]
fn do_intern_mut<'a>(
&mut self,
s: &'a [u8],
alloc: impl FnOnce(&Bump, &'a [u8]) -> &'static [u8],
) -> S {
let hash = self.hash(s);
get_or_insert(self.strs.get_mut(), &self.arena, s, hash, self.map.get_mut(), alloc)
}
#[inline]
fn hash(&self, s: &[u8]) -> u64 {
use std::hash::Hasher;
let mut h = self.hash_builder.build_hasher();
h.write(s);
h.finish()
}
}
#[inline]
fn get_or_insert<'a, S: InternerSymbol>(
strs: &mut Vec<&'static [u8]>,
arena: &Bump,
s: &'a [u8],
hash: u64,
map: &mut Map<S>,
alloc: impl FnOnce(&Bump, &'a [u8]) -> &'static [u8],
) -> S {
match map.entry(hash, mk_eq(s), hasher) {
hash_table::Entry::Occupied(e) => e.get().1,
hash_table::Entry::Vacant(e) => {
let s = alloc(arena, s);
let i = strs.len();
let new_sym = S::from_usize(i);
strs.push(s);
e.insert(((hash, s), new_sym));
new_sym
}
}
}
#[inline]
fn hasher<S>(((hash, _), _): &RawMapKey<S>) -> u64 {
*hash
}
#[inline]
fn mk_eq<S>(s: &[u8]) -> impl Fn(&RawMapKey<S>) -> bool + Copy + '_ {
move |((_, ss), _): &RawMapKey<S>| s == *ss
}
#[inline]
fn alloc(arena: &Bump, s: &[u8]) -> &'static [u8] {
unsafe { std::mem::transmute::<&[u8], &'static [u8]>(arena.alloc_slice_copy(s)) }
}
#[inline]
fn no_alloc(_: &Bump, s: &'static [u8]) -> &'static [u8] {
s
}
#[inline]
fn no_alloc_unchecked(_: &Bump, s: &[u8]) -> &'static [u8] {
unsafe { std::mem::transmute::<&[u8], &'static [u8]>(s) }
}