use std::alloc::{Layout, alloc, dealloc};
use std::mem::MaybeUninit;
use std::num::NonZero;
use std::ptr::NonNull;
use std::sync::atomic::{self, AtomicUsize};
use crate::mem::{Block, BlockRef, BlockRefDynamic, BlockRefVTable, BlockSize};
#[must_use]
pub fn allocate(len: NonZero<BlockSize>) -> Block {
let block_ptr = new_block(len);
let block = unsafe { block_ptr.as_ref() };
let block_ref = unsafe { BlockRef::new(block_ptr, &BLOCK_REF_FNS) };
unsafe { Block::new(block.ptr, block.len, block_ref) }
}
#[derive(Debug)]
struct StdAllocBlock {
ptr: NonNull<MaybeUninit<u8>>,
len: NonZero<BlockSize>,
ref_count: AtomicUsize,
}
unsafe impl BlockRefDynamic for StdAllocBlock {
type State = Self;
#[cfg_attr(test, mutants::skip)] fn clone(state_ptr: NonNull<Self::State>) -> NonNull<Self::State> {
let state = unsafe { state_ptr.as_ref() };
state.ref_count.fetch_add(1, atomic::Ordering::Relaxed);
state_ptr
}
#[cfg_attr(test, mutants::skip)] fn drop(state_ptr: NonNull<Self::State>) {
let state = unsafe { state_ptr.as_ref() };
if state.ref_count.fetch_sub(1, atomic::Ordering::Release) != 1 {
return;
}
atomic::fence(atomic::Ordering::Acquire);
unsafe { dealloc(state.ptr.as_ptr().cast(), byte_array_layout(state.len)) };
unsafe {
dealloc(state_ptr.as_ptr().cast(), BLOCK_LAYOUT);
}
}
}
const BLOCK_REF_FNS: BlockRefVTable<StdAllocBlock> = BlockRefVTable::from_trait();
fn byte_array_layout(len: NonZero<BlockSize>) -> Layout {
Layout::array::<u8>(len.get() as usize).expect("the layout of a byte array can always be determined")
}
const BLOCK_LAYOUT: Layout = unsafe { Layout::from_size_align_unchecked(size_of::<StdAllocBlock>(), align_of::<StdAllocBlock>()) };
fn new_block(len: NonZero<BlockSize>) -> NonNull<StdAllocBlock> {
let capacity_ptr = NonNull::new(unsafe { alloc(byte_array_layout(len)) })
.expect("we do not intend to handle failed allocations - they are fatal")
.cast::<MaybeUninit<u8>>();
let block_ptr = NonNull::new(unsafe { alloc(BLOCK_LAYOUT) })
.expect("we do not intend to handle failed allocations - they are fatal")
.cast::<StdAllocBlock>();
let block = StdAllocBlock {
ptr: capacity_ptr,
len,
ref_count: AtomicUsize::new(1),
};
unsafe { block_ptr.write(block) };
block_ptr
}