#![doc = include_str!("readme.md")]
use crate::tree::TokenProvenance;
use std::{
alloc::{Layout, alloc, dealloc},
cell::{RefCell, UnsafeCell},
ptr::{NonNull, copy_nonoverlapping},
};
const CHUNK_SIZE: usize = 64 * 1024;
const ALIGN: usize = 8;
thread_local! {
static CHUNK_POOL: RefCell<Vec<NonNull<u8>>> = RefCell::new(Vec::with_capacity(16))
}
pub struct SyntaxArena {
ptr: UnsafeCell<NonNull<u8>>,
end: UnsafeCell<NonNull<u8>>,
full_chunks: UnsafeCell<Vec<(NonNull<u8>, usize)>>,
current_chunk_start: UnsafeCell<NonNull<u8>>,
metadata: UnsafeCell<Vec<TokenProvenance>>,
}
impl SyntaxArena {
pub fn new(capacity: usize) -> Self {
let dangling = unsafe { NonNull::new_unchecked(ALIGN as *mut u8) };
Self { ptr: UnsafeCell::new(dangling), end: UnsafeCell::new(dangling), full_chunks: UnsafeCell::new(Vec::with_capacity(capacity)), current_chunk_start: UnsafeCell::new(NonNull::dangling()), metadata: UnsafeCell::new(Vec::new()) }
}
pub fn add_metadata(&self, provenance: TokenProvenance) -> std::num::NonZeroU32 {
let metadata = unsafe { &mut *self.metadata.get() };
metadata.push(provenance);
std::num::NonZeroU32::new(metadata.len() as u32).expect("Metadata index overflow")
}
pub fn get_metadata(&self, index: std::num::NonZeroU32) -> Option<&TokenProvenance> {
let metadata = unsafe { &*self.metadata.get() };
metadata.get(index.get() as usize - 1)
}
#[inline(always)]
pub fn alloc<T>(&self, value: T) -> &mut T {
let layout = Layout::new::<T>();
debug_assert!(layout.align() <= ALIGN);
unsafe {
let ptr = self.alloc_raw(layout.size());
let ptr = ptr.as_ptr() as *mut T;
ptr.write(value);
&mut *ptr
}
}
#[inline(always)]
pub fn alloc_slice_copy<T: Copy>(&self, slice: &[T]) -> &mut [T] {
if slice.is_empty() {
return &mut [];
}
let layout = Layout::for_value(slice);
debug_assert!(layout.align() <= ALIGN);
unsafe {
let ptr = self.alloc_raw(layout.size());
let ptr = ptr.as_ptr() as *mut T;
copy_nonoverlapping(slice.as_ptr(), ptr, slice.len());
std::slice::from_raw_parts_mut(ptr, slice.len())
}
}
#[inline(always)]
pub fn alloc_slice_fill_iter<T, I>(&self, count: usize, iter: I) -> &mut [T]
where
I: IntoIterator<Item = T>,
{
if count == 0 {
return &mut [];
}
let layout = Layout::array::<T>(count).unwrap();
debug_assert!(layout.align() <= ALIGN);
unsafe {
let ptr = self.alloc_raw(layout.size());
let base_ptr = ptr.as_ptr() as *mut T;
let mut i = 0;
for item in iter {
if i >= count {
break;
}
base_ptr.add(i).write(item);
i += 1
}
debug_assert_eq!(i, count, "Iterator yielded fewer items than expected");
std::slice::from_raw_parts_mut(base_ptr, count)
}
}
#[inline(always)]
unsafe fn alloc_raw(&self, size: usize) -> NonNull<u8> {
unsafe {
let ptr = *self.ptr.get();
let end = *self.end.get();
let current_addr = ptr.as_ptr() as usize;
debug_assert!(current_addr % ALIGN == 0);
let next_addr = (current_addr + size + ALIGN - 1) & !(ALIGN - 1);
if std::intrinsics::likely(next_addr <= end.as_ptr() as usize) {
*self.ptr.get() = NonNull::new_unchecked(next_addr as *mut u8);
return ptr;
}
self.alloc_slow(size)
}
}
#[inline(always)]
unsafe fn alloc_raw_aligned(&self, size: usize, align: usize) -> NonNull<u8> {
unsafe {
let ptr = *self.ptr.get();
let end = *self.end.get();
let current_addr = ptr.as_ptr() as usize;
let aligned_addr = (current_addr + align - 1) & !(align - 1);
let next_addr = aligned_addr + size;
if std::intrinsics::likely(next_addr <= end.as_ptr() as usize) {
*self.ptr.get() = NonNull::new_unchecked(next_addr as *mut u8);
return NonNull::new_unchecked(aligned_addr as *mut u8);
}
self.alloc_slow_aligned(size, align)
}
}
#[inline(never)]
unsafe fn alloc_slow_aligned(&self, size: usize, align: usize) -> NonNull<u8> {
unsafe {
let current_start = *self.current_chunk_start.get();
if current_start != NonNull::dangling() {
let current_end = (*self.end.get()).as_ptr() as usize;
let actual_size = current_end - current_start.as_ptr() as usize;
(*self.full_chunks.get()).push((current_start, actual_size))
}
let alloc_size = usize::max(size + align, CHUNK_SIZE);
let chunk_ptr = Self::alloc_chunk_aligned(alloc_size, align);
*self.current_chunk_start.get() = chunk_ptr;
let start_addr = chunk_ptr.as_ptr() as usize;
let result_ptr = NonNull::new_unchecked(start_addr as *mut u8);
let next_free = start_addr + size;
*self.ptr.get() = NonNull::new_unchecked(next_free as *mut u8);
*self.end.get() = NonNull::new_unchecked((start_addr + alloc_size) as *mut u8);
result_ptr
}
}
unsafe fn alloc_chunk_aligned(size: usize, align: usize) -> NonNull<u8> {
let layout = Layout::from_size_align(size, align).unwrap();
let ptr = alloc(layout);
if ptr.is_null() {
std::alloc::handle_alloc_error(layout)
}
NonNull::new_unchecked(ptr)
}
#[inline(never)]
unsafe fn alloc_slow(&self, size: usize) -> NonNull<u8> {
unsafe {
let current_start = *self.current_chunk_start.get();
if current_start != NonNull::dangling() {
let current_end = (*self.end.get()).as_ptr() as usize;
let actual_size = current_end - current_start.as_ptr() as usize;
(*self.full_chunks.get()).push((current_start, actual_size))
}
let alloc_size = usize::max(size + ALIGN, CHUNK_SIZE);
let chunk_ptr = Self::alloc_chunk(alloc_size);
*self.current_chunk_start.get() = chunk_ptr;
let start_addr = chunk_ptr.as_ptr() as usize;
let result_ptr = NonNull::new_unchecked(start_addr as *mut u8);
let next_free = (start_addr + size + ALIGN - 1) & !(ALIGN - 1);
*self.ptr.get() = NonNull::new_unchecked(next_free as *mut u8);
*self.end.get() = NonNull::new_unchecked((start_addr + alloc_size) as *mut u8);
result_ptr
}
}
unsafe fn alloc_chunk(size: usize) -> NonNull<u8> {
if size == CHUNK_SIZE {
let ptr = CHUNK_POOL.try_with(|pool| pool.borrow_mut().pop());
if let Ok(Some(ptr)) = ptr {
return ptr;
}
}
let layout = Layout::from_size_align(size, ALIGN).unwrap();
unsafe {
let ptr = alloc(layout);
if ptr.is_null() {
std::alloc::handle_alloc_error(layout)
}
NonNull::new_unchecked(ptr)
}
}
}
impl Drop for SyntaxArena {
fn drop(&mut self) {
unsafe {
let current = *self.current_chunk_start.get();
if current != NonNull::dangling() {
let current_end = (*self.end.get()).as_ptr() as usize;
let actual_size = current_end - current.as_ptr() as usize;
Self::recycle_chunk(current, actual_size)
}
for (ptr, size) in (*self.full_chunks.get()).iter() {
Self::recycle_chunk(*ptr, *size)
}
}
}
}
impl SyntaxArena {
unsafe fn recycle_chunk(ptr: NonNull<u8>, size: usize) {
if size == CHUNK_SIZE {
let _ = CHUNK_POOL
.try_with(|pool| {
let mut pool = pool.borrow_mut();
if pool.len() < 64 {
pool.push(ptr)
}
else {
unsafe {
let layout = Layout::from_size_align(size, ALIGN).unwrap();
dealloc(ptr.as_ptr(), layout);
}
}
})
.unwrap_or_else(|_| {
unsafe {
let layout = Layout::from_size_align(size, ALIGN).unwrap();
dealloc(ptr.as_ptr(), layout);
}
});
return;
}
let layout = Layout::from_size_align(size, ALIGN).unwrap();
unsafe { dealloc(ptr.as_ptr(), layout) }
}
}
unsafe impl Send for SyntaxArena {}
unsafe impl Sync for SyntaxArena {}
impl Default for SyntaxArena {
fn default() -> Self {
Self::new(16)
}
}