use std::cell::RefCell;
#[cfg(feature = "fast-arena")]
pub struct FastTermArena {
count: RefCell<u32>,
cache: RefCell<Vec<u64>>,
cache_mask: usize,
stats: RefCell<FastArenaStats>,
}
#[derive(Debug, Clone, Default)]
pub struct FastArenaStats {
pub allocations: u64,
pub cache_hits: u64,
pub cache_misses: u64,
pub resets: u64,
pub peak_terms: u32,
}
impl FastArenaStats {
pub fn cache_hit_rate(&self) -> f64 {
let total = self.cache_hits + self.cache_misses;
if total == 0 { 0.0 } else { self.cache_hits as f64 / total as f64 }
}
}
#[cfg(feature = "fast-arena")]
impl FastTermArena {
pub fn with_capacity(expected_terms: usize) -> Self {
let cache_cap = (expected_terms * 2).next_power_of_two().max(64);
Self {
count: RefCell::new(0),
cache: RefCell::new(vec![0u64; cache_cap * 2]),
cache_mask: cache_cap - 1,
stats: RefCell::new(FastArenaStats::default()),
}
}
pub fn new() -> Self {
Self::with_capacity(4096)
}
#[inline]
pub fn intern(&self, hash: u64) -> (u32, bool) {
let mask = self.cache_mask;
let cache = self.cache.borrow();
let start = (hash as usize) & mask;
for offset in 0..4 {
let slot = (start + offset) & mask;
let stored_hash = cache[slot * 2];
if stored_hash == hash && hash != 0 {
let id = cache[slot * 2 + 1] as u32;
drop(cache);
self.stats.borrow_mut().cache_hits += 1;
return (id, true);
}
if stored_hash == 0 {
break; }
}
drop(cache);
self.stats.borrow_mut().cache_misses += 1;
self.alloc_with_hash(hash)
}
fn alloc_with_hash(&self, hash: u64) -> (u32, bool) {
let mut count = self.count.borrow_mut();
let id = *count;
*count = count.checked_add(1).expect("FastTermArena: term overflow");
let mut stats = self.stats.borrow_mut();
stats.allocations += 1;
if id + 1 > stats.peak_terms {
stats.peak_terms = id + 1;
}
drop(stats);
if hash != 0 {
let mask = self.cache_mask;
let mut cache = self.cache.borrow_mut();
let start = (hash as usize) & mask;
for offset in 0..8 {
let slot = (start + offset) & mask;
if cache[slot * 2] == 0 {
cache[slot * 2] = hash;
cache[slot * 2 + 1] = id as u64;
break;
}
}
}
drop(count);
(id, false)
}
pub fn alloc(&self) -> u32 {
let mut count = self.count.borrow_mut();
let id = *count;
*count = count.checked_add(1).expect("FastTermArena: term overflow");
self.stats.borrow_mut().allocations += 1;
id
}
pub fn reset(&self) {
*self.count.borrow_mut() = 0;
self.cache.borrow_mut().fill(0);
self.stats.borrow_mut().resets += 1;
}
pub fn term_count(&self) -> u32 {
*self.count.borrow()
}
pub fn stats(&self) -> FastArenaStats {
self.stats.borrow().clone()
}
}
#[cfg(feature = "fast-arena")]
impl Default for FastTermArena {
fn default() -> Self {
Self::new()
}
}
#[inline]
pub fn fx_hash_u64(value: u64) -> u64 {
value.wrapping_mul(0x517cc1b727220a95)
}
#[inline]
pub fn fx_hash_pair(a: u32, b: u32) -> u64 {
fx_hash_u64((a as u64) << 32 | b as u64)
}
#[inline]
pub fn fx_hash_str(s: &str) -> u64 {
let mut h: u64 = 0;
for &b in s.as_bytes() {
h = h.wrapping_mul(0x100000001b3) ^ (b as u64);
}
fx_hash_u64(h)
}
#[cfg(test)]
#[cfg(feature = "fast-arena")]
mod tests {
use super::*;
#[test]
fn test_arena_alloc() {
let arena = FastTermArena::new();
let id0 = arena.alloc();
let id1 = arena.alloc();
assert_eq!(id0, 0);
assert_eq!(id1, 1);
assert_eq!(arena.term_count(), 2);
}
#[test]
fn test_arena_intern_dedup() {
let arena = FastTermArena::new();
let (id1, hit1) = arena.intern(0x12345678);
let (id2, hit2) = arena.intern(0x12345678);
assert!(!hit1, "first intern should be a miss");
assert!(hit2, "second intern should be a hit");
assert_eq!(id1, id2, "same hash should return same ID");
}
#[test]
fn test_arena_intern_different() {
let arena = FastTermArena::new();
let (id1, _) = arena.intern(0xAAAA);
let (id2, _) = arena.intern(0xBBBB);
assert_ne!(id1, id2);
}
#[test]
fn test_arena_reset() {
let arena = FastTermArena::new();
arena.alloc();
arena.alloc();
assert_eq!(arena.term_count(), 2);
arena.reset();
assert_eq!(arena.term_count(), 0);
}
#[test]
fn test_arena_stats() {
let arena = FastTermArena::new();
arena.intern(0x111);
arena.intern(0x111); arena.intern(0x222); let stats = arena.stats();
assert_eq!(stats.cache_hits, 1);
assert_eq!(stats.cache_misses, 2);
assert!(stats.cache_hit_rate() > 0.3);
}
#[test]
fn test_arena_capacity() {
let arena = FastTermArena::with_capacity(16);
for i in 0..16u64 {
arena.intern(i + 1);
}
assert_eq!(arena.term_count(), 16);
}
#[test]
fn test_fx_hash_deterministic() {
assert_eq!(fx_hash_u64(42), fx_hash_u64(42));
assert_ne!(fx_hash_u64(42), fx_hash_u64(43));
}
#[test]
fn test_fx_hash_pair() {
let h1 = fx_hash_pair(1, 2);
let h2 = fx_hash_pair(2, 1);
assert_ne!(h1, h2, "order should matter");
}
#[test]
fn test_fx_hash_str() {
assert_eq!(fx_hash_str("Nat"), fx_hash_str("Nat"));
assert_ne!(fx_hash_str("Nat"), fx_hash_str("Vec"));
}
#[test]
fn test_arena_high_volume() {
let arena = FastTermArena::with_capacity(10_000);
for i in 0..10_000u64 {
arena.intern(i + 1);
}
assert_eq!(arena.term_count(), 10_000);
for i in 0..10_000u64 {
let (_, hit) = arena.intern(i + 1);
assert!(hit, "re-intern should hit cache");
}
assert!(arena.stats().cache_hit_rate() > 0.49);
}
}