#![allow(unsafe_code)]
use std::collections::{HashMap, HashSet};
use std::ptr::NonNull;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicUsize, Ordering};
use bumpalo::Bump;
pub struct Dict {
refcount: AtomicUsize,
parent: *mut Dict,
inner: Mutex<DictInner>,
}
struct DictInner {
table: HashMap<Vec<u8>, NonNull<[u8]>>,
owned: HashSet<usize>,
retained_arenas: Vec<Arc<Bump>>,
}
impl Drop for DictInner {
fn drop(&mut self) {
for &canonical in self.table.values() {
unsafe { drop(Box::from_raw(canonical.as_ptr())); }
}
}
}
impl Dict {
pub fn new_refcounted() -> *mut Dict {
Self::new_with_parent(std::ptr::null_mut())
}
pub unsafe fn new_sub(parent: *mut Dict) -> *mut Dict {
if !parent.is_null() {
unsafe { (*parent).add_ref(); }
}
Self::new_with_parent(parent)
}
fn new_with_parent(parent: *mut Dict) -> *mut Dict {
let boxed = Box::new(Self {
refcount: AtomicUsize::new(1),
parent,
inner: Mutex::new(DictInner {
table: HashMap::new(),
owned: HashSet::new(),
retained_arenas: Vec::new(),
}),
});
Box::into_raw(boxed)
}
fn ancestor_lookup(&self, input: &[u8]) -> *const u8 {
let mut cur = self.parent;
while !cur.is_null() {
let d = unsafe { &*cur };
let hit = d.inner.lock().expect("Dict mutex poisoned")
.table.get(input).map(|&c| Self::canonical_ptr(c));
if let Some(p) = hit { return p; }
cur = d.parent;
}
std::ptr::null()
}
pub fn retain_arena(&self, arena: Arc<Bump>) {
let mut g = self.inner.lock().unwrap();
if g.retained_arenas.iter().any(|a| Arc::ptr_eq(a, &arena)) {
return;
}
g.retained_arenas.push(arena);
}
pub fn add_ref(&self) {
self.refcount.fetch_add(1, Ordering::Relaxed);
}
pub unsafe fn release(ptr: *mut Dict) -> bool {
let prev = unsafe { (*ptr).refcount.fetch_sub(1, Ordering::Release) };
if prev == 1 {
std::sync::atomic::fence(Ordering::Acquire);
let parent = unsafe { (*ptr).parent };
unsafe { drop(Box::from_raw(ptr)); }
if !parent.is_null() {
unsafe { Dict::release(parent); }
}
true
} else {
false
}
}
pub fn refcount(&self) -> usize {
self.refcount.load(Ordering::Relaxed)
}
pub fn intern(&self, input: &[u8]) -> *const u8 {
let ancestor = self.ancestor_lookup(input);
if !ancestor.is_null() {
return ancestor;
}
let mut inner = self.inner.lock().expect("Dict mutex poisoned");
if let Some(&canonical) = inner.table.get(input) {
return Self::canonical_ptr(canonical);
}
let mut buf = Vec::with_capacity(input.len() + 1);
buf.extend_from_slice(input);
buf.push(0);
let canonical = unsafe {
NonNull::new_unchecked(Box::into_raw(buf.into_boxed_slice()))
};
let ptr = Self::canonical_ptr(canonical);
inner.owned.insert(ptr as usize);
inner.table.insert(input.to_vec(), canonical);
ptr
}
#[inline]
fn canonical_ptr(canonical: NonNull<[u8]>) -> *const u8 {
canonical.cast::<u8>().as_ptr()
}
pub fn intern_str(&self, s: &str) -> *const u8 {
self.intern(s.as_bytes())
}
pub fn lookup(&self, input: &[u8]) -> *const u8 {
let local = self.inner
.lock().expect("Dict mutex poisoned")
.table
.get(input)
.map(|&c| Self::canonical_ptr(c));
match local {
Some(p) => p,
None => self.ancestor_lookup(input),
}
}
pub fn owns(&self, ptr: *const u8) -> bool {
if self.inner
.lock().expect("Dict mutex poisoned")
.owned.contains(&(ptr as usize))
{
return true;
}
let mut cur = self.parent;
while !cur.is_null() {
let d = unsafe { &*cur };
if d.inner.lock().expect("Dict mutex poisoned")
.owned.contains(&(ptr as usize))
{
return true;
}
cur = d.parent;
}
false
}
pub fn len(&self) -> usize {
self.inner.lock().expect("Dict mutex poisoned").table.len()
}
pub fn is_empty(&self) -> bool { self.len() == 0 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn refcount_lifecycle() {
let p = Dict::new_refcounted();
assert_eq!(unsafe { (*p).refcount() }, 1);
unsafe { (*p).add_ref(); }
assert_eq!(unsafe { (*p).refcount() }, 2);
let freed = unsafe { Dict::release(p) };
assert!(!freed);
assert_eq!(unsafe { (*p).refcount() }, 1);
let freed = unsafe { Dict::release(p) };
assert!(freed);
}
#[test]
fn intern_returns_stable_pointer() {
let p = Dict::new_refcounted();
let d = unsafe { &*p };
let p1 = d.intern(b"div");
let p2 = d.intern(b"div");
assert_eq!(p1, p2);
assert_eq!(unsafe { *p1.add(3) }, 0);
let slice = unsafe { std::slice::from_raw_parts(p1, 3) };
assert_eq!(slice, b"div");
unsafe { Dict::release(p); }
}
#[test]
fn different_inputs_different_pointers() {
let p = Dict::new_refcounted();
let d = unsafe { &*p };
let pa = d.intern(b"p");
let pb = d.intern(b"span");
assert_ne!(pa, pb);
assert_eq!(d.len(), 2);
unsafe { Dict::release(p); }
}
#[test]
fn owns_recognises_canonicals_only() {
let p = Dict::new_refcounted();
let d = unsafe { &*p };
let canonical = d.intern(b"strong");
assert!(d.owns(canonical));
let foreign: *const u8 = b"strong".as_ptr();
assert!(!d.owns(foreign));
unsafe { Dict::release(p); }
}
#[test]
fn lookup_returns_null_on_miss() {
let p = Dict::new_refcounted();
let d = unsafe { &*p };
assert!(d.lookup(b"unknown").is_null());
d.intern(b"known");
assert!(!d.lookup(b"known").is_null());
unsafe { Dict::release(p); }
}
#[test]
fn sub_dict_shares_parent_interned_pointers() {
let parent = Dict::new_refcounted();
let p = unsafe { &*parent };
let name_in_parent = p.intern(b"v");
let sub = unsafe { Dict::new_sub(parent) };
let s = unsafe { &*sub };
assert_eq!(s.intern(b"v"), name_in_parent);
assert_eq!(s.lookup(b"v"), name_in_parent);
assert!(s.owns(name_in_parent));
let child_only = s.intern(b"child");
assert!(s.owns(child_only));
assert!(!p.owns(child_only));
assert!(p.lookup(b"child").is_null());
assert_eq!(p.refcount(), 2);
unsafe { Dict::release(sub); }
assert_eq!(p.refcount(), 1);
unsafe { Dict::release(parent); }
}
#[test]
fn retains_foreign_arena_and_keeps_it_alive() {
use std::sync::Weak;
let dst = Dict::new_refcounted();
let dst_d = unsafe { &*dst };
let weak: Weak<Bump>;
{
let src_arena = Arc::new(Bump::new());
weak = Arc::downgrade(&src_arena);
dst_d.retain_arena(Arc::clone(&src_arena));
}
assert!(weak.upgrade().is_some(), "destination dict must keep the foreign arena alive");
let again = weak.upgrade().unwrap();
dst_d.retain_arena(Arc::clone(&again));
assert_eq!(dst_d.inner.lock().unwrap().retained_arenas.len(), 1);
drop(again);
unsafe { Dict::release(dst); }
assert!(weak.upgrade().is_none(), "arena should free once the destination dict drops");
}
}