use prelude::*;
use core::{mem, ops};
use {brk, sync};
use bookkeeper::{self, Bookkeeper, Allocator};
use shim::config;
#[cfg(feature = "tls")]
use tls;
#[cfg(feature = "tls")]
type ThreadLocalAllocator = MoveCell<Option<LazyInit<fn() -> LocalAllocator, LocalAllocator>>>;
static GLOBAL_ALLOCATOR: sync::Mutex<LazyInit<fn() -> GlobalAllocator, GlobalAllocator>> =
sync::Mutex::new(LazyInit::new(GlobalAllocator::init));
#[cfg(feature = "tls")]
tls! {
static THREAD_ALLOCATOR: ThreadLocalAllocator = MoveCell::new(Some(LazyInit::new(LocalAllocator::init)));
}
macro_rules! get_allocator {
(|$v:ident| $b:expr) => {{
#[cfg(feature = "tls")]
{
THREAD_ALLOCATOR.with(|thread_alloc| {
if let Some(mut thread_alloc_original) = thread_alloc.replace(None) {
let res = {
let $v = thread_alloc_original.get();
$b
};
thread_alloc.replace(Some(thread_alloc_original));
res
} else {
log!(WARNING, "Accessing the allocator after deinitialization of the local allocator.");
let mut guard = GLOBAL_ALLOCATOR.lock();
let $v = guard.get();
$b
}
})
}
#[cfg(not(feature = "tls"))]
{
let mut guard = GLOBAL_ALLOCATOR.lock();
let $v = guard.get();
$b
}
}}
}
macro_rules! derive_deref {
($imp:ty, $target:ty) => {
impl ops::Deref for $imp {
type Target = $target;
fn deref(&self) -> &$target {
&self.inner
}
}
impl ops::DerefMut for $imp {
fn deref_mut(&mut self) -> &mut $target {
&mut self.inner
}
}
};
}
struct GlobalAllocator {
inner: Bookkeeper,
}
impl GlobalAllocator {
fn init() -> GlobalAllocator {
log!(NOTE, "Initializing the global allocator.");
let (aligner, initial_segment, excessive) =
brk::lock().canonical_brk(4 * bookkeeper::EXTRA_ELEMENTS * mem::size_of::<Block>(), mem::align_of::<Block>());
let mut res = GlobalAllocator {
inner: Bookkeeper::new(unsafe {
Vec::from_raw_parts(initial_segment, 0)
}),
};
res.push(aligner);
res.push(excessive);
res
}
}
derive_deref!(GlobalAllocator, Bookkeeper);
impl Allocator for GlobalAllocator {
#[inline]
fn alloc_fresh(&mut self, size: usize, align: usize) -> Block {
let (alignment_block, res, excessive) = brk::lock().canonical_brk(size, align);
self.push(alignment_block);
self.push(excessive);
res
}
fn on_new_memory(&mut self) {
if self.total_bytes() > config::OS_MEMTRIM_LIMIT {
let block = self.pop().expect("The byte count on the global allocator is invalid.");
if block.size() >= config::OS_MEMTRIM_WORTHY {
log!(NOTE, "Memtrimming the global allocator.");
if let Err(block) = brk::lock().release(block) {
self.push(block);
}
} else {
log!(WARNING, "Memtrimming for the global allocator failed.");
self.push(block);
}
}
}
}
#[cfg(feature = "tls")]
pub struct LocalAllocator {
inner: Bookkeeper,
}
impl LocalAllocator {
#[cfg(feature = "tls")]
fn init() -> LocalAllocator {
extern fn dtor(alloc: &ThreadLocalAllocator) {
log!(NOTE, "Deinitializing and freeing the local allocator.");
let alloc = alloc.replace(None).expect("Thread-local allocator is already freed.");
let mut global_alloc = GLOBAL_ALLOCATOR.lock();
let global_alloc = global_alloc.get();
alloc.into_inner().inner.for_each(move |block| global_alloc.free(block));
}
log!(NOTE, "Initializing the local allocator.");
let initial_segment = GLOBAL_ALLOCATOR
.lock()
.get()
.alloc(4 * bookkeeper::EXTRA_ELEMENTS * mem::size_of::<Block>(), mem::align_of::<Block>());
unsafe {
THREAD_ALLOCATOR.register_thread_destructor(dtor);
LocalAllocator {
inner: Bookkeeper::new(Vec::from_raw_parts(initial_segment, 0)),
}
}
}
}
#[cfg(feature = "tls")]
derive_deref!(LocalAllocator, Bookkeeper);
#[cfg(feature = "tls")]
impl Allocator for LocalAllocator {
#[inline]
fn alloc_fresh(&mut self, size: usize, align: usize) -> Block {
GLOBAL_ALLOCATOR.lock().get().alloc(size, align)
}
#[inline]
fn on_new_memory(&mut self) {
if self.total_bytes() < config::FRAGMENTATION_SCALE * self.len()
|| self.total_bytes() > config::LOCAL_MEMTRIM_LIMIT {
log!(NOTE, "Memtrimming the local allocator.");
let mut global_alloc = GLOBAL_ALLOCATOR.lock();
let global_alloc = global_alloc.get();
while let Some(block) = self.pop() {
global_alloc.free(block);
if self.total_bytes() < config::LOCAL_MEMTRIM_STOP { break; }
}
}
}
}
#[inline]
pub fn alloc(size: usize, align: usize) -> *mut u8 {
log!(CALL, "Allocating buffer of size {} (align {}).", size, align);
get_allocator!(|alloc| *Pointer::from(alloc.alloc(size, align)))
}
#[inline]
pub unsafe fn free(ptr: *mut u8, size: usize) {
log!(CALL, "Freeing buffer of size {}.", size);
get_allocator!(|alloc| alloc.free(Block::from_raw_parts(Pointer::new(ptr), size)))
}
#[inline]
pub unsafe fn realloc(ptr: *mut u8, old_size: usize, size: usize, align: usize) -> *mut u8 {
log!(CALL, "Reallocating buffer of size {} to new size {}.", old_size, size);
get_allocator!(|alloc| {
*Pointer::from(alloc.realloc(
Block::from_raw_parts(Pointer::new(ptr), old_size),
size,
align
))
})
}
#[inline]
pub unsafe fn realloc_inplace(ptr: *mut u8, old_size: usize, size: usize) -> Result<(), ()> {
log!(CALL, "Inplace reallocating buffer of size {} to new size {}.", old_size, size);
get_allocator!(|alloc| {
if alloc.realloc_inplace(
Block::from_raw_parts(Pointer::new(ptr), old_size),
size
).is_ok() {
Ok(())
} else {
Err(())
}
})
}