use std::cell::RefCell;
use std::sync::RwLock;
pub const TAG_STATIC: u64 = 0b00 << 62;
pub const TAG_ARENA: u64 = 0b01 << 62;
pub const TAG_RES: u64 = 0b10 << 62;
pub const TAG_MASK: u64 = 0b11 << 62;
const DEFAULT_ARENA_CAPACITY: usize = 64 * 1024;
pub struct CycleArena {
buffer: Vec<u8>,
cursor: usize,
}
impl Default for CycleArena {
fn default() -> Self {
Self::new()
}
}
impl CycleArena {
pub fn new() -> Self {
Self {
buffer: vec![0u8; DEFAULT_ARENA_CAPACITY],
cursor: 0,
}
}
#[inline(always)]
pub fn reset(&mut self) {
self.cursor = 0;
}
#[inline]
pub fn alloc_bytes(&mut self, len: usize) -> &mut [u8] {
if self.cursor + len > self.buffer.len() {
let new_cap = (self.buffer.len() * 2).max(self.cursor + len);
self.buffer.resize(new_cap, 0);
}
let start = self.cursor;
self.cursor += len;
&mut self.buffer[start..self.cursor]
}
#[inline]
pub fn put_bytes(&mut self, bytes: &[u8]) -> u64 {
let len = bytes.len();
let offset = self.cursor;
let dest = self.alloc_bytes(len);
dest.copy_from_slice(bytes);
encode_arena_handle(offset as u32, len as u32)
}
#[inline]
pub fn put_str(&mut self, s: &str) -> u64 {
self.put_bytes(s.as_bytes())
}
#[inline]
pub fn resolve_str(&self, handle: u64) -> &str {
match handle & TAG_MASK {
TAG_STATIC => StaticInterner::resolve(handle as u32),
TAG_ARENA => {
let (offset, len) = decode_arena_handle(handle);
let bytes = &self.buffer[offset as usize..(offset + len) as usize];
unsafe { std::str::from_utf8_unchecked(bytes) }
}
_ => "",
}
}
#[inline]
pub fn resolve_bytes(&self, handle: u64) -> &[u8] {
match handle & TAG_MASK {
TAG_STATIC => StaticInterner::resolve(handle as u32).as_bytes(),
TAG_ARENA => {
let (offset, len) = decode_arena_handle(handle);
&self.buffer[offset as usize..(offset + len) as usize]
}
_ => &[],
}
}
}
#[inline(always)]
pub fn encode_arena_handle(offset: u32, len: u32) -> u64 {
TAG_ARENA | ((offset as u64 & 0x7FFF_FFFF) << 31) | (len as u64 & 0x7FFF_FFFF)
}
#[inline(always)]
pub fn decode_arena_handle(handle: u64) -> (u32, u32) {
let offset = ((handle >> 31) & 0x7FFF_FFFF) as u32;
let len = (handle & 0x7FFF_FFFF) as u32;
(offset, len)
}
thread_local! {
pub static THREAD_CYCLE_ARENA: RefCell<CycleArena> = RefCell::new(CycleArena::new());
}
#[inline]
pub fn with_cycle_arena<R>(f: impl FnOnce(&mut CycleArena) -> R) -> R {
THREAD_CYCLE_ARENA.with(|arena| f(&mut arena.borrow_mut()))
}
#[inline]
pub fn resolve_thread_str(handle: u64) -> &'static str {
THREAD_CYCLE_ARENA.with(|arena| {
let a = arena.borrow();
match handle & TAG_MASK {
TAG_STATIC => StaticInterner::resolve(handle as u32),
TAG_ARENA => {
let (offset, len) = decode_arena_handle(handle);
let bytes = &a.buffer[offset as usize..(offset + len) as usize];
unsafe { std::mem::transmute::<&str, &'static str>(std::str::from_utf8_unchecked(bytes)) }
}
_ => "",
}
})
}
#[inline]
pub fn resolve_thread_bytes(handle: u64) -> &'static [u8] {
THREAD_CYCLE_ARENA.with(|arena| {
let a = arena.borrow();
match handle & TAG_MASK {
TAG_STATIC => StaticInterner::resolve(handle as u32).as_bytes(),
TAG_ARENA => {
let (offset, len) = decode_arena_handle(handle);
let bytes = &a.buffer[offset as usize..(offset + len) as usize];
unsafe { std::mem::transmute::<&[u8], &'static [u8]>(bytes) }
}
_ => &[],
}
})
}
#[inline]
pub fn put_thread_str(s: &str) -> u64 {
THREAD_CYCLE_ARENA.with(|arena| arena.borrow_mut().put_str(s))
}
#[inline]
pub fn put_thread_bytes(b: &[u8]) -> u64 {
THREAD_CYCLE_ARENA.with(|arena| arena.borrow_mut().put_bytes(b))
}
pub struct StaticInterner;
static STATIC_STRINGS: RwLock<Vec<&'static str>> = RwLock::new(Vec::new());
impl StaticInterner {
pub fn intern(s: &str) -> u64 {
let mut table = STATIC_STRINGS.write().unwrap();
if let Some((idx, _)) = table.iter().enumerate().find(|&(_, entry)| *entry == s) {
return TAG_STATIC | (idx as u64);
}
let leaked: &'static str = Box::leak(s.to_string().into_boxed_str());
let idx = table.len();
table.push(leaked);
TAG_STATIC | (idx as u64)
}
pub fn resolve(id: u32) -> &'static str {
let table = STATIC_STRINGS.read().unwrap();
if let Some(&s) = table.get(id as usize) {
s
} else {
""
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arena_alloc_and_resolve() {
let mut arena = CycleArena::new();
let h1 = arena.put_str("hello");
let h2 = arena.put_str("world");
assert_eq!(arena.resolve_str(h1), "hello");
assert_eq!(arena.resolve_str(h2), "world");
arena.reset();
let h3 = arena.put_str("fresh");
assert_eq!(arena.resolve_str(h3), "fresh");
}
#[test]
fn static_interner_roundtrip() {
let h1 = StaticInterner::intern("test_constant");
let h2 = StaticInterner::intern("test_constant");
assert_eq!(h1, h2, "interning same string must return identical handle");
let arena = CycleArena::new();
assert_eq!(arena.resolve_str(h1), "test_constant");
}
}