use std::{
cell::UnsafeCell, fmt::Debug, mem::MaybeUninit, ops::Deref, ptr::NonNull,
sync::atomic::AtomicUsize,
};
pub trait IndexType:
Copy
+ TryFrom<usize, Error: std::fmt::Debug>
+ TryInto<usize, Error: std::fmt::Debug>
+ PartialEq
+ Eq
+ Debug
{
}
impl IndexType for u16 {}
impl IndexType for u32 {}
impl IndexType for u64 {}
impl IndexType for u128 {}
impl IndexType for usize {}
#[repr(C)]
struct Header {
ref_count: AtomicUsize,
alloc_index: AtomicUsize,
capacity: usize,
}
#[repr(C)]
struct Buffer<T> {
header: Header,
data: UnsafeCell<[MaybeUninit<T>]>,
}
impl<T> Buffer<T> {
fn layout_for_capacity(capacity: usize) -> std::alloc::Layout {
let header_layout = std::alloc::Layout::new::<Header>();
let array_layout = std::alloc::Layout::array::<MaybeUninit<T>>(capacity)
.expect("Failed to create array layout for buffer data");
let (layout, _offset) = header_layout
.extend(array_layout)
.expect("Failed to extend header layout with data layout");
layout
}
fn left_space(&self) -> usize {
self.header.capacity
- self
.header
.alloc_index
.load(std::sync::atomic::Ordering::Acquire)
}
}
impl<T> Drop for Buffer<T> {
fn drop(&mut self) {
let len = self
.header
.alloc_index
.load(std::sync::atomic::Ordering::Acquire);
unsafe {
let data = &mut *self.data.get();
data.iter_mut().take(len).for_each(|item| {
item.assume_init_drop();
});
}
}
}
struct ChunkRef<T> {
inner: Option<NonNull<Buffer<T>>>,
}
impl<T> Clone for ChunkRef<T> {
fn clone(&self) -> Self {
if self.inner.is_none() {
return Self { inner: None };
}
let buffer = unsafe { self.inner.as_ref().unwrap().as_ref() };
buffer
.header
.ref_count
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Self { inner: self.inner }
}
}
impl<T> Drop for ChunkRef<T> {
fn drop(&mut self) {
if self.inner.is_none() {
return;
}
let inner_ref = self.inner.as_ref().unwrap();
let buffer = unsafe { inner_ref.as_ref() };
if buffer
.header
.ref_count
.fetch_sub(1, std::sync::atomic::Ordering::AcqRel)
== 1
{
unsafe {
let layout = Buffer::<T>::layout_for_capacity(buffer.header.capacity);
std::ptr::drop_in_place(inner_ref.as_ptr());
std::alloc::dealloc(inner_ref.as_ptr() as *mut u8, layout);
}
}
}
}
impl<T> ChunkRef<T> {
fn new(capacity: usize) -> Self {
let layout = Buffer::<T>::layout_for_capacity(capacity);
let ptr = unsafe { std::alloc::alloc(layout) };
if ptr.is_null() {
std::alloc::handle_alloc_error(layout);
}
let ptr = std::ptr::slice_from_raw_parts_mut(ptr as *mut (), capacity) as *mut Buffer<T>;
unsafe {
let header = &mut (*ptr).header;
header
.ref_count
.store(1, std::sync::atomic::Ordering::Release);
header
.alloc_index
.store(0, std::sync::atomic::Ordering::Release);
header.capacity = capacity;
}
Self {
inner: Some(NonNull::new(ptr).expect("Failed to create NonNull pointer")),
}
}
unsafe fn buffer(&self) -> &Buffer<T> {
unsafe {
self.inner
.as_ref()
.expect("Dereferenced null chunk")
.as_ref()
}
}
fn is_null(&self) -> bool {
self.inner.is_none()
}
fn null() -> Self {
Self { inner: None }
}
}
pub struct ArcSlice<T, L: IndexType = u32> {
chunk: ChunkRef<T>,
index: L,
len: L,
}
unsafe impl<T, L: IndexType> Send for ArcSlice<T, L> where T: Send + Sync {}
unsafe impl<T, L: IndexType> Sync for ArcSlice<T, L> where T: Send + Sync {}
impl<T, L: IndexType> Clone for ArcSlice<T, L> {
fn clone(&self) -> Self {
Self {
chunk: self.chunk.clone(),
index: self.index,
len: self.len,
}
}
}
impl<T, L: IndexType> ArcSlice<T, L> {
pub fn get(&self) -> &[T] {
if self.chunk.is_null() {
return &[];
}
unsafe {
let buffer = self.chunk.buffer();
let data = &*buffer.data.get();
let start = self
.index
.try_into()
.expect("Index exceeds index type capacity");
let len = self
.len
.try_into()
.expect("Length exceeds index type capacity");
let slice = &data[start..start + len];
std::mem::transmute::<&[MaybeUninit<T>], &[T]>(slice)
}
}
pub fn empty() -> Self {
Self {
chunk: ChunkRef::null(),
index: 0
.try_into()
.expect("Index 0 should be valid for any index type"),
len: 0
.try_into()
.expect("Length 0 should be valid for any index type"),
}
}
}
impl<T, L: IndexType> Deref for ArcSlice<T, L> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.get()
}
}
pub struct ArcSingle<T, L: IndexType = u32> {
chunk: ChunkRef<T>,
index: L,
}
unsafe impl<T, L: IndexType> Send for ArcSingle<T, L> where T: Send + Sync {}
unsafe impl<T, L: IndexType> Sync for ArcSingle<T, L> where T: Send + Sync {}
impl<T, L: IndexType> Clone for ArcSingle<T, L> {
fn clone(&self) -> Self {
Self {
chunk: self.chunk.clone(),
index: self.index,
}
}
}
impl<T, L: IndexType> ArcSingle<T, L> {
pub fn get(&self) -> &T {
unsafe {
let buffer = self.chunk.buffer();
let data = &*buffer.data.get();
let start = self
.index
.try_into()
.expect("Index exceeds index type capacity");
let slice = &data[start];
std::mem::transmute::<&MaybeUninit<T>, &T>(slice)
}
}
pub fn from_slice(handle: ArcSlice<T, L>) -> Self {
assert_eq!(
handle.len,
1.try_into().expect("Length exceeds index type capacity"),
"ArcSingle can only be created from a slice of length 1"
);
Self {
chunk: handle.chunk.clone(),
index: handle.index,
}
}
}
impl<T, L: IndexType> Deref for ArcSingle<T, L> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.get()
}
}
impl<T, L: IndexType> TryFrom<ArcSlice<T, L>> for ArcSingle<T, L> {
type Error = &'static str;
fn try_from(value: ArcSlice<T, L>) -> Result<Self, Self::Error> {
if value.len != 1.try_into().expect("Length exceeds index type capacity") {
return Err("ArcSingle can only be created from a slice of length 1");
}
Ok(Self {
chunk: value.chunk,
index: value.index,
})
}
}
impl<T, L: IndexType> From<ArcSingle<T, L>> for ArcSlice<T, L> {
fn from(value: ArcSingle<T, L>) -> Self {
Self {
chunk: value.chunk,
index: value.index,
len: 1.try_into().expect("Length exceeds index type capacity"),
}
}
}
pub struct Allocator<T, L: IndexType = u32, const N: usize = 256> {
chunk: ChunkRef<T>,
phantom: std::marker::PhantomData<L>,
}
impl<T, L: IndexType, const N: usize> std::fmt::Debug for Allocator<T, L, N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Allocator<T={}, L={}, N={}>",
std::any::type_name::<T>(),
std::any::type_name::<L>(),
N
)
}
}
impl<T, L: IndexType, const N: usize> Default for Allocator<T, L, N> {
fn default() -> Self {
Self::new()
}
}
impl<T, L: IndexType, const N: usize> Allocator<T, L, N> {
pub fn new() -> Self {
Self {
chunk: ChunkRef::new(N),
phantom: std::marker::PhantomData,
}
}
pub fn alloc<F>(&mut self, len: L, mut init: F) -> ArcSlice<T, L>
where
F: FnMut(usize) -> T,
{
if len == 0.try_into().expect("Length exceeds index type capacity") {
return ArcSlice {
chunk: ChunkRef::null(),
index: 0
.try_into()
.expect("Index 0 should be valid for any index type"),
len: 0
.try_into()
.expect("Length 0 should be valid for any index type"),
};
}
let len: usize = len.try_into().expect("Length exceeds index type capacity");
let left_space = unsafe { self.chunk.buffer().left_space() };
if N < len {
let chunk: ChunkRef<T> = ChunkRef::new(len);
unsafe {
let buffer = chunk.buffer(); let data = &mut *buffer.data.get();
for (i, item) in data.iter_mut().take(len).enumerate() {
item.as_mut_ptr().write(init(i));
}
buffer
.header
.alloc_index
.store(len, std::sync::atomic::Ordering::Release);
}
return ArcSlice {
chunk,
index: 0
.try_into()
.expect("Index 0 should be valid for any index type"),
len: len.try_into().expect("Length exceeds index type capacity"),
};
}
if left_space < len {
let chunk: ChunkRef<T> = ChunkRef::new(N);
unsafe {
let buffer = chunk.buffer(); let data = &mut *buffer.data.get();
for (i, item) in data.iter_mut().take(len).enumerate() {
item.as_mut_ptr().write(init(i));
}
buffer
.header
.alloc_index
.store(len, std::sync::atomic::Ordering::Release);
}
if left_space < N - len {
self.chunk = chunk.clone();
}
return ArcSlice {
chunk,
index: 0
.try_into()
.expect("Index 0 should be valid for any index type"),
len: len.try_into().expect("Length exceeds index type capacity"),
};
}
unsafe {
let buffer = self.chunk.buffer();
let index = buffer
.header
.alloc_index
.load(std::sync::atomic::Ordering::Relaxed);
let base_ptr = buffer.data.get() as *mut MaybeUninit<T>;
for i in 0..len {
let val = init(i);
base_ptr.add(index + i).write(MaybeUninit::new(val));
}
buffer
.header
.alloc_index
.store(index + len, std::sync::atomic::Ordering::Release);
ArcSlice {
chunk: self.chunk.clone(),
index: index.try_into().expect("Index cap"),
len: len.try_into().expect("Length cap"),
}
}
}
pub fn try_alloc<F, E>(&mut self, len: L, mut init: F) -> Result<ArcSlice<T, L>, E>
where
F: FnMut(usize) -> Result<T, E>,
{
if len == 0.try_into().expect("Length exceeds index type capacity") {
return Ok(ArcSlice {
chunk: ChunkRef::null(),
index: 0
.try_into()
.expect("Index 0 should be valid for any index type"),
len: 0
.try_into()
.expect("Length 0 should be valid for any index type"),
});
}
let len: usize = len.try_into().expect("Length exceeds index type capacity");
let left_space = unsafe { self.chunk.buffer().left_space() };
if N < len {
let chunk: ChunkRef<T> = ChunkRef::new(len);
unsafe {
let buffer = chunk.buffer(); let data = &mut *buffer.data.get();
for (i, item) in data.iter_mut().take(len).enumerate() {
match init(i) {
Ok(val) => item.as_mut_ptr().write(val),
Err(e) => {
for item in data.iter_mut().take(i) {
item.assume_init_drop();
}
return Err(e);
}
}
}
buffer
.header
.alloc_index
.store(len, std::sync::atomic::Ordering::Release);
}
return Ok(ArcSlice {
chunk,
index: 0
.try_into()
.expect("Index 0 should be valid for any index type"),
len: len.try_into().expect("Length exceeds index type capacity"),
});
}
if left_space < len {
let chunk: ChunkRef<T> = ChunkRef::new(N);
unsafe {
let buffer = chunk.buffer(); let data = &mut *buffer.data.get();
for (i, item) in data.iter_mut().take(len).enumerate() {
match init(i) {
Ok(val) => item.as_mut_ptr().write(val),
Err(e) => {
for item in data.iter_mut().take(i) {
item.assume_init_drop();
}
return Err(e);
}
}
}
buffer
.header
.alloc_index
.store(len, std::sync::atomic::Ordering::Release);
}
if left_space < N - len {
self.chunk = chunk.clone();
}
return Ok(ArcSlice {
chunk,
index: 0
.try_into()
.expect("Index 0 should be valid for any index type"),
len: len.try_into().expect("Length exceeds index type capacity"),
});
}
unsafe {
let buffer = self.chunk.buffer();
let index = buffer
.header
.alloc_index
.load(std::sync::atomic::Ordering::Relaxed);
let base_ptr = buffer.data.get() as *mut MaybeUninit<T>;
for i in 0..len {
match init(i) {
Ok(val) => base_ptr.add(index + i).write(MaybeUninit::new(val)),
Err(e) => {
for j in 0..i {
std::ptr::drop_in_place(base_ptr.add(index + j) as *mut T);
}
return Err(e);
}
}
}
buffer
.header
.alloc_index
.store(index + len, std::sync::atomic::Ordering::Release);
Ok(ArcSlice {
chunk: self.chunk.clone(),
index: index.try_into().expect("Index cap"),
len: len.try_into().expect("Length cap"),
})
}
}
pub fn alloc_single<F>(&mut self, init: F) -> ArcSingle<T, L>
where
F: FnOnce() -> T,
{
let mut init = Some(init);
let slice = self.alloc(
1.try_into().expect("Length exceeds index type capacity"),
|_| init.take().expect("init called more than once")(),
);
ArcSingle::from_slice(slice)
}
pub fn try_alloc_single<F, E>(&mut self, init: F) -> Result<ArcSingle<T, L>, E>
where
F: FnOnce() -> Result<T, E>,
{
let mut init = Some(init);
self.try_alloc(
1.try_into().expect("Length exceeds index type capacity"),
|_| init.take().expect("init called more than once")(),
)
.map(ArcSingle::from_slice)
}
pub fn alloc_value(&mut self, value: T) -> ArcSingle<T, L> {
self.alloc_single(|| value)
}
pub fn alloc_empty() -> ArcSlice<T, L> {
ArcSlice::empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn test_allocator() {
let mut allocator: Allocator<u32, u32, 16> = Allocator::new();
let handle1 = allocator.alloc(10, |i| i as u32);
let handle2 = allocator.alloc(20, |i| (i + 10) as u32);
let handle3 = handle1.clone();
assert_eq!(handle1.get(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
assert_eq!(
handle2.get(),
&[
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29
]
);
assert_eq!(handle3.get(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
}
#[derive(Debug)]
struct DropCounter<'a> {
#[allow(dead_code)]
value: usize,
drops: &'a AtomicUsize,
}
impl<'a> Drop for DropCounter<'a> {
fn drop(&mut self) {
self.drops.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn test_drop_runs_exactly_once_per_item() {
static DROPS: AtomicUsize = AtomicUsize::new(0);
{
let mut allocator: Allocator<DropCounter, u32, 8> = Allocator::new();
let handle = allocator.alloc(8, |i| DropCounter {
value: i,
drops: &DROPS,
});
assert_eq!(handle.get().len(), 8);
let _handle2 = handle.clone();
assert_eq!(DROPS.load(Ordering::SeqCst), 0);
}
assert_eq!(DROPS.load(Ordering::SeqCst), 8);
}
#[test]
fn test_multiple_chunks_and_large_allocs() {
static DROPS: AtomicUsize = AtomicUsize::new(0);
{
let mut allocator: Allocator<DropCounter, u32, 4> = Allocator::new();
let h1 = allocator.alloc(3, |i| DropCounter {
value: i,
drops: &DROPS,
});
let h2 = allocator.alloc(2, |i| DropCounter {
value: i + 3,
drops: &DROPS,
});
let h3 = allocator.alloc(10, |i| DropCounter {
value: i + 5,
drops: &DROPS,
});
assert_eq!(h1.get().len(), 3);
assert_eq!(h2.get().len(), 2);
assert_eq!(h3.get().len(), 10);
let _ = (h1, h2, h3);
}
assert_eq!(DROPS.load(Ordering::SeqCst), 15);
}
#[test]
fn test_zst_allocations() {
let mut allocator: Allocator<(), u32, 16> = Allocator::new();
let h1 = allocator.alloc(0, |_| ());
let h2 = allocator.alloc(8, |_| ());
assert_eq!(h1.get().len(), 0);
assert_eq!(h2.get().len(), 8);
}
#[test]
fn test_handle_survives_chunk_rotation() {
let mut allocator: Allocator<u32, u32, 4> = Allocator::new();
let h1 = allocator.alloc(4, |i| i as u32);
let _h2 = allocator.alloc(3, |i| (i + 10) as u32);
let h3 = allocator.alloc(4, |i| (i + 20) as u32);
assert_eq!(h1.get(), &[0, 1, 2, 3]);
assert_eq!(h3.get(), &[20, 21, 22, 23]);
}
#[test]
fn test_many_small_allocations() {
let mut allocator: Allocator<u32, u32, 8> = Allocator::new();
let mut handles = Vec::new();
for i in 0..32 {
let h = allocator.alloc(1, |_| i as u32);
handles.push(h);
}
for (i, h) in handles.iter().enumerate() {
assert_eq!(h.get(), &[(i as u32)]);
}
}
#[cfg(miri)]
mod miri_tests {
use super::*;
use std::cell::Cell;
#[test]
fn miri_stress_realloc_and_drop() {
let mut allocator: Allocator<u64, u32, 8> = Allocator::new();
let h1 = allocator.alloc(8, |i| i as u64);
let h2 = allocator.alloc(1, |i| (i + 100) as u64);
let h3 = allocator.alloc(16, |i| (i + 200) as u64);
assert_eq!(h1.get()[0], 0);
assert_eq!(h2.get()[0], 100);
assert_eq!(h3.get()[0], 200);
let _ = (h1, h2, h3);
}
#[derive(Debug)]
struct Poison<'a> {
alive: &'a Cell<bool>,
}
impl<'a> Drop for Poison<'a> {
fn drop(&mut self) {
self.alive.set(false);
}
}
#[test]
fn miri_use_after_free_guard() {
let flag = Cell::new(true);
{
let mut allocator: Allocator<Poison, u32, 4> = Allocator::new();
let handle = allocator.alloc(4, |_| Poison { alive: &flag });
assert!(flag.get());
drop(handle);
assert!(flag.get());
}
assert!(!flag.get());
}
#[test]
fn miri_many_small_allocations() {
let mut allocator: Allocator<u64, u32, 4> = Allocator::new();
let mut handles = Vec::new();
for i in 0..64u64 {
let h = allocator.alloc(1, |_| i);
handles.push(h);
}
for (i, h) in handles.iter().enumerate() {
assert_eq!(h.get(), &[(i as u64)]);
}
}
#[test]
fn miri_clone_stress() {
let mut allocator: Allocator<u32, u32, 8> = Allocator::new();
let h = allocator.alloc(8, |i| i as u32);
let mut clones = Vec::new();
for _ in 0..32 {
clones.push(h.clone());
}
for c in clones.iter() {
assert_eq!(c.get()[0], 0);
}
drop(h);
for c in clones.iter() {
assert_eq!(c.get()[7], 7);
}
}
}
}