use std::ptr;
use std::mem;
#[allow(unused_imports)]
use super::slag::{compute_metadata, CoarseAllocator, Creek, DirtyFn, LocalCache, MagazineCache,
MemoryBlock, Metadata, PageAlloc, RevocablePipe, Slag};
use super::utils::{mmap, Lazy, TypedArray};
#[cfg(feature = "nightly")]
use std::intrinsics::likely;
#[cfg(not(feature = "nightly"))]
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
fn likely(b: bool) -> bool {
b
}
pub mod global {
#[allow(unused_imports)]
use super::{CoarseAllocator, Creek, DirtyFn, ElfMalloc, MemoryBlock, ObjectAlloc, PageAlloc,
TieredSizeClasses, TypedArray};
#[cfg(feature = "nightly")]
use super::likely;
use std::ptr;
use std::cell::UnsafeCell;
use std::mem;
#[allow(unused_imports)]
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc::{channel, Sender};
use std::sync::Mutex;
use std::thread;
type Block = Creek;
type PA = PageAlloc<Block, ()>;
unsafe fn dirty_slag(mem: *mut u8) {
trace!("dirtying {:?}", mem);
let usable_size = 32 << 10;
let base_page = 4096;
let mut cur_addr = mem.offset(base_page);
while cur_addr < mem.offset(usable_size) {
cur_addr = cur_addr.offset(base_page);
(*(cur_addr as *mut AtomicUsize)).compare_and_swap(0, 1, Ordering::Relaxed);
}
}
#[derive(Clone)]
struct BackgroundDirty;
impl DirtyFn for BackgroundDirty {
fn dirty(_mem: *mut u8) {
#[cfg(feature = "nightly")]
{
let _ = LOCAL_DESTRUCTOR_CHAN.try_with(|h| h.send(Husk::Slag(_mem)));
}
}
}
#[cfg(all(feature = "nightly", target_thread_local))]
#[thread_local]
static mut INIT: bool = false;
#[cfg(all(feature = "nightly", target_thread_local))]
#[thread_local]
static mut PTR: *mut ElfMalloc<PA, TieredSizeClasses<ObjectAlloc<PA>>> = ptr::null_mut();
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
fn init_begin() {
#[cfg(feature = "nightly")]
#[cfg(target_thread_local)]
unsafe {
INIT = true;
}
#[cfg(feature = "nightly")]
#[cfg(not(target_thread_local))]
{
INITIALIZING.fetch_add(1, Ordering::Relaxed);
}
#[cfg(not(feature = "nightly"))]
{
INITIALIZING.fetch_add(1, Ordering::Relaxed);
}
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
fn init_end() {
#[cfg(feature = "nightly")]
#[cfg(target_thread_local)]
unsafe {
INIT = false;
}
#[cfg(feature = "nightly")]
#[cfg(not(target_thread_local))]
{
INITIALIZING.fetch_sub(1, Ordering::Relaxed);
}
#[cfg(not(feature = "nightly"))]
{
INITIALIZING.fetch_sub(1, Ordering::Relaxed);
}
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
fn is_initializing() -> bool {
#[cfg(feature = "nightly")]
#[cfg(target_thread_local)]
unsafe { INIT }
#[cfg(feature = "nightly")]
#[cfg(not(target_thread_local))]
{
INITIALIZING.load(Ordering::Relaxed) > 0
}
#[cfg(not(feature = "nightly"))]
{
INITIALIZING.load(Ordering::Relaxed) > 0
}
}
#[derive(Clone)]
struct GlobalAllocator {
inner: ElfMalloc<PA, TieredSizeClasses<ObjectAlloc<PA>>>,
}
unsafe impl Send for GlobalAllocator {}
unsafe impl Sync for GlobalAllocator {}
impl GlobalAllocator {
fn new() -> GlobalAllocator {
GlobalAllocator { inner: ElfMalloc::new() }
}
}
enum Husk<T> {
Array(TypedArray<T>),
Obj(T),
#[allow(dead_code)]
Ptr(*mut u8),
#[allow(dead_code)]
Slag(*mut u8),
}
unsafe impl<T> Send for Husk<T> {}
impl Drop for GlobalAllocator {
fn drop(&mut self) {
#[cfg(not(feature = "nightly"))]
{
let chan = DESTRUCTOR_CHAN.lock().unwrap().clone();
unsafe {
let _ =
chan.send(Husk::Array(ptr::read(&self.inner.allocs.small_objs.classes)));
let _ =
chan.send(Husk::Array(ptr::read(&self.inner.allocs.medium_objs.classes)));
let sc = Husk::Obj(self.inner.allocs.word_objs.take().unwrap());
let _ = chan.send(sc);
}
}
#[cfg(feature = "nightly")]
{
#[cfg(target_thread_local)]
#[thread_local]
{
unsafe {
PTR = ptr::null_mut();
}
}
LOCAL_DESTRUCTOR_CHAN.try_with(|chan| unsafe {
let _ = chan.send(Husk::Array(ptr::read(&self.inner
.allocs
.small_objs
.classes)));
let _ = chan.send(Husk::Array(ptr::read(&self.inner
.allocs
.medium_objs
.classes)));
let sc = Husk::Obj(self.inner.allocs.word_objs.take().unwrap());
let _ = chan.send(sc);
})
.unwrap_or_else(|_| unsafe {
let chan = DESTRUCTOR_CHAN.lock().unwrap().clone();
let _ = chan.send(Husk::Array(ptr::read(&self.inner
.allocs
.small_objs
.classes)));
let _ = chan.send(Husk::Array(ptr::read(&self.inner
.allocs
.medium_objs
.classes)));
let sc = Husk::Obj(self.inner.allocs.word_objs.take().unwrap());
let _ = chan.send(sc);
})
}
}
}
lazy_static! {
static ref ELF_HEAP: GlobalAllocator = GlobalAllocator::new();
static ref DESTRUCTOR_CHAN: Mutex<Sender<Husk<ObjectAlloc<PA>>>> = {
let (sender, receiver) = channel();
thread::spawn(move || unsafe {
let mut local_alloc = ELF_HEAP.clone();
loop {
if let Ok(msg) = receiver.recv() {
let msg: Husk<_> = msg;
match msg {
Husk::Array(tarray) => {
for p in tarray.iter() {
ptr::drop_in_place(p);
}
tarray.destroy();
},
Husk::Ptr(p) => local_alloc.inner.free(p),
Husk::Slag(s) => dirty_slag(s),
Husk::Obj(t) => mem::drop(t),
}
continue
}
return;
}
});
Mutex::new(sender)
};
}
#[allow(dead_code)]
lazy_static!{
static ref INITIALIZING: AtomicUsize = AtomicUsize::new(0);
}
thread_local! {
static LOCAL_DESTRUCTOR_CHAN: Sender<Husk<ObjectAlloc<PA>>> =
DESTRUCTOR_CHAN.lock().unwrap().clone();
static LOCAL_ELF_HEAP: UnsafeCell<GlobalAllocator> = UnsafeCell::new(ELF_HEAP.clone());
}
pub unsafe fn alloc(size: usize) -> *mut u8 {
#[cfg(feature = "nightly")]
#[cfg(target_thread_local)]
#[thread_local]
{
if likely(!PTR.is_null()) {
return (*PTR).alloc(size);
}
}
if is_initializing() {
return super::large_alloc::alloc(size);
}
init_begin();
let res = alloc_inner(size);
init_end();
res
}
unsafe fn alloc_inner(size: usize) -> *mut u8 {
#[cfg(feature = "nightly")]
{
LOCAL_ELF_HEAP.try_with(|h| {
let res = (*h.get()).inner.alloc(size);
PTR = &mut (*h.get()).inner as *mut _;
res
})
.unwrap_or_else(|_| super::large_alloc::alloc(size))
}
#[cfg(not(feature = "nightly"))]
{
LOCAL_ELF_HEAP.with(|h| (*h.get()).inner.alloc(size))
}
}
unsafe fn realloc_inner(item: *mut u8, size: usize) -> *mut u8 {
LOCAL_ELF_HEAP.with(|h| (*h.get()).inner.realloc(item, size))
}
pub unsafe fn realloc(item: *mut u8, new_size: usize) -> *mut u8 {
assert!(!is_initializing(), "realloc can't be called recursively");
init_begin();
let res = realloc_inner(item, new_size);
init_end();
res
}
pub unsafe fn free(item: *mut u8) {
#[cfg(feature = "nightly")]
{
#[cfg(target_thread_local)]
#[thread_local]
{
if likely(!PTR.is_null()) {
return (*PTR).free(item);
}
}
LOCAL_ELF_HEAP.try_with(|h| (*h.get()).inner.free(item))
.unwrap_or_else(|_| if !ELF_HEAP.inner.pages.backing_memory().contains(item) {
super::large_alloc::free(item);
} else {
let chan = DESTRUCTOR_CHAN.lock().unwrap().clone();
let _ = chan.send(Husk::Ptr(item));
})
}
#[cfg(not(feature = "nightly"))]
{
LOCAL_ELF_HEAP.with(|h| (*h.get()).inner.free(item))
}
}
}
trait AllocMap<T>
where Self: Sized
{
type Key;
fn init<F: FnMut(Self::Key) -> T>(start: Self::Key, n_classes: usize, f: F) -> Self {
Self::init_conserve(start, n_classes, f).1
}
fn init_conserve<F: FnMut(Self::Key) -> T>(start: Self::Key,
n_classes: usize,
f: F)
-> (F, Self);
unsafe fn get_raw(&self, k: Self::Key) -> *mut T;
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
unsafe fn get(&self, k: Self::Key) -> &T {
&*self.get_raw(k)
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
unsafe fn get_mut(&mut self, k: Self::Key) -> &mut T {
&mut *self.get_raw(k)
}
fn foreach<F: Fn(*mut T)>(&self, f: F);
fn max_key(&self) -> Self::Key;
}
struct TieredSizeClasses<T> {
word_objs: Option<T>,
small_objs: Multiples<T>,
medium_objs: PowersOfTwo<T>,
}
impl<T> AllocMap<T> for TieredSizeClasses<T> {
type Key = usize;
fn init_conserve<F: FnMut(usize) -> T>(start: usize, n_classes: usize, f: F) -> (F, Self) {
let n_small_classes = n_classes / 2;
let n_medium_classes = n_classes - n_small_classes;
let (f2, small_classes) = Multiples::init_conserve(start, n_small_classes, f);
let (mut f3, medium_classes) =
PowersOfTwo::init_conserve(small_classes.max_key() + 1, n_medium_classes, f2);
let word_objs = f3(8);
(f3,
TieredSizeClasses {
word_objs: Some(word_objs),
small_objs: small_classes,
medium_objs: medium_classes,
})
}
unsafe fn get_raw(&self, n: usize) -> *mut T {
if n <= 8 {
self.word_objs.as_ref().unwrap() as *const _ as *mut T
} else if n <= self.small_objs.max_key() {
self.small_objs.get_raw(n)
} else {
self.medium_objs.get_raw(n)
}
}
#[inline]
fn max_key(&self) -> usize {
self.medium_objs.max_key()
}
fn foreach<F: Fn(*mut T)>(&self, f: F) {
self.small_objs.foreach(&f);
self.medium_objs.foreach(f);
}
}
const MULTIPLE: usize = 16;
struct Multiples<T> {
starting_size: usize,
max_size: usize,
classes: TypedArray<T>,
}
#[inline]
fn round_up(n: usize) -> usize {
(n + (MULTIPLE - 1)) & !(MULTIPLE - 1)
}
impl<T> AllocMap<T> for Multiples<T> {
type Key = usize;
fn init_conserve<F: FnMut(usize) -> T>(start: usize, n_classes: usize, mut f: F) -> (F, Self) {
debug_assert!(n_classes >= 1);
let starting_size = round_up(start);
let res = Multiples {
starting_size: starting_size,
max_size: n_classes * MULTIPLE + starting_size - MULTIPLE,
classes: TypedArray::new(n_classes),
};
let mut cur_size = res.starting_size;
for p in res.classes.iter() {
unsafe {
ptr::write(p, f(cur_size));
}
cur_size += MULTIPLE;
}
debug_assert_eq!(res.max_size, cur_size - MULTIPLE);
(f, res)
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
unsafe fn get_raw(&self, n: usize) -> *mut T {
let class = round_up(n);
debug_assert!(class <= self.max_size);
self.classes
.get((round_up(n) - self.starting_size) / MULTIPLE)
}
#[inline]
fn max_key(&self) -> usize {
self.max_size
}
fn foreach<F: Fn(*mut T)>(&self, f: F) {
for class in self.classes.iter() {
f(class)
}
}
}
struct PowersOfTwo<T> {
starting_size: usize,
max_size: usize,
classes: TypedArray<T>,
}
impl Drop for DynamicAllocator {
fn drop(&mut self) {
self.0.allocs.foreach(|x| unsafe { ptr::drop_in_place(x) });
unsafe {
self.0.allocs.medium_objs.classes.destroy();
self.0.allocs.small_objs.classes.destroy();
}
}
}
impl<T> PowersOfTwo<T> {
fn new(start_from: usize, n_classes: usize) -> PowersOfTwo<T> {
PowersOfTwo {
starting_size: start_from.next_power_of_two(),
max_size: 0, classes: TypedArray::new(n_classes),
}
}
}
impl<T> AllocMap<T> for PowersOfTwo<T> {
type Key = usize;
fn init_conserve<F: FnMut(Self::Key) -> T>(start: usize,
n_classes: usize,
mut f: F)
-> (F, Self) {
let mut res = Self::new(start, n_classes);
let mut cur_size = res.starting_size;
unsafe {
for item in res.classes.iter() {
ptr::write(item, f(cur_size));
cur_size *= 2;
}
}
res.max_size = cur_size / 2;
(f, res)
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
unsafe fn get_raw(&self, k: usize) -> *mut T {
debug_assert!(k <= self.max_size);
let log = (k.next_power_of_two().trailing_zeros() -
self.starting_size.trailing_zeros()) as usize;
debug_assert!(log < self.classes.len(),
"log={} len={}",
log,
self.classes.len());
self.classes.get(log)
}
#[inline]
fn max_key(&self) -> usize {
self.max_size
}
fn foreach<F: Fn(*mut T)>(&self, f: F) {
for class in self.classes.iter() {
f(class)
}
}
}
#[derive(Clone)]
pub struct DynamicAllocator(ElfMalloc<PageAlloc<Creek>,
TieredSizeClasses<ObjectAlloc<PageAlloc<Creek>>>>);
unsafe impl Send for DynamicAllocator {}
impl DynamicAllocator {
pub fn new() -> Self {
DynamicAllocator(ElfMalloc::new())
}
pub unsafe fn alloc(&mut self, size: usize) -> *mut u8 {
self.0.alloc(size)
}
pub unsafe fn free(&mut self, item: *mut u8) {
self.0.free(item)
}
}
#[cfg(not(feature = "local_cache"))]
type ObjectAlloc<CA> = Lazy<MagazineCache<CA>>;
#[cfg(feature = "local_cache")]
type ObjectAlloc<CA> = Lazy<LocalCache<CA>>;
struct ElfMalloc<CA: CoarseAllocator, AM: AllocMap<ObjectAlloc<CA>>> {
pages: CA,
allocs: AM,
max_size: usize,
start_from: usize,
n_classes: usize,
}
impl Default for DynamicAllocator {
fn default() -> Self {
Self::new()
}
}
impl<M: MemoryBlock, D: DirtyFn> ElfMalloc<PageAlloc<M, D>,
TieredSizeClasses<ObjectAlloc<PageAlloc<M, D>>>> {
fn new() -> Self {
let pa = PageAlloc::new(1 << 21, 1 << 20);
Self::new_internal(128 << 10, 0.6, pa, 8, 25)
}
}
impl<M: MemoryBlock, D: DirtyFn, AM: AllocMap<ObjectAlloc<PageAlloc<M, D>>, Key = usize>> Clone
for ElfMalloc<PageAlloc<M, D>, AM> {
fn clone(&self) -> Self {
let new_map = AM::init(self.start_from, self.n_classes, |size: usize| unsafe {
self.allocs.get(size).clone()
});
ElfMalloc {
pages: self.pages.clone(),
allocs: new_map,
max_size: self.max_size,
start_from: self.start_from,
n_classes: self.n_classes,
}
}
}
impl<M: MemoryBlock, D: DirtyFn, AM: AllocMap<ObjectAlloc<PageAlloc<M, D>>, Key = usize>>
ElfMalloc<PageAlloc<M, D>, AM> {
fn new_internal(usable_size: usize,
cutoff_factor: f64,
pa: PageAlloc<M, D>,
start_from: usize,
n_classes: usize)
-> Self {
use self::mmap::map;
let mut meta_pointer = map(mem::size_of::<Metadata>() * n_classes) as *mut Metadata;
let am = AM::init(start_from, n_classes, |size: usize| {
let u_size = if size < usable_size / 4 {
usable_size
} else {
1 << 50
};
let m_ptr = meta_pointer;
unsafe {
meta_pointer = meta_pointer.offset(1);
ptr::write(m_ptr,
compute_metadata(size,
pa.backing_memory().page_size(),
0,
cutoff_factor,
u_size));
}
let params = (m_ptr, 1 << 20, pa.clone(), RevocablePipe::new_size(8));
ObjectAlloc::new(params)
});
let max_size = am.max_key();
ElfMalloc {
pages: pa.clone(),
allocs: am,
max_size: max_size,
start_from: start_from,
n_classes: n_classes,
}
}
unsafe fn alloc(&mut self, bytes: usize) -> *mut u8 {
if likely(bytes < self.max_size) {
self.allocs.get_mut(bytes).alloc()
} else {
large_alloc::alloc(bytes)
}
}
unsafe fn realloc(&mut self, item: *mut u8, new_size: usize) -> *mut u8 {
if item.is_null() {
return self.alloc(new_size);
}
if new_size == 0 {
self.free(item);
return ptr::null_mut();
}
if likely(self.pages.backing_memory().contains(item)) {
let slag = &*Slag::find(item, self.pages.backing_memory().page_size());
let meta = slag.get_metadata();
if meta.object_size >= new_size {
return item;
}
let new_memory = self.alloc(new_size);
ptr::copy_nonoverlapping(item, new_memory, meta.object_size);
self.free(item);
new_memory
} else {
let (size, _) = large_alloc::get_commitment(item);
if size >= new_size {
return item;
}
let new_memory = self.alloc(new_size);
ptr::copy_nonoverlapping(item, new_memory, size);
new_memory
}
}
unsafe fn free(&mut self, item: *mut u8) {
if likely(self.pages.backing_memory().contains(item)) {
let slag = &*Slag::find(item, self.pages.backing_memory().page_size());
self.allocs
.get_mut(slag.get_metadata().object_size)
.free(item)
} else {
large_alloc::free(item)
}
}
}
mod large_alloc {
#[cfg(test)]
use std::collections::HashMap;
#[cfg(test)]
use std::cell::RefCell;
#[cfg(test)]
thread_local! {
pub static SEEN_PTRS: RefCell<HashMap<*mut u8, usize>> = RefCell::new(HashMap::new());
}
use super::mmap::{map, unmap};
const PAGE_SIZE: isize = 4096;
pub unsafe fn alloc(size: usize) -> *mut u8 {
let mem = map(size + PAGE_SIZE as usize);
*(mem as *mut usize) = size + PAGE_SIZE as usize;
let res = mem.offset(PAGE_SIZE);
debug_assert!(!mem.is_null());
let upage = PAGE_SIZE as usize;
debug_assert_eq!(mem as usize % upage, 0);
debug_assert_eq!(res as usize % upage, 0);
#[cfg(test)]
SEEN_PTRS.with(|hs| hs.borrow_mut().insert(mem, size + PAGE_SIZE as usize));
res
}
pub unsafe fn free(item: *mut u8) {
let base_ptr = item.offset(-PAGE_SIZE);
#[cfg(debug_assertions)]
{
use std::ptr;
ptr::write_volatile(item, 10);
}
let upage = PAGE_SIZE as usize;
debug_assert_eq!(item as usize % upage, 0);
debug_assert_eq!(base_ptr as usize % upage, 0);
#[cfg(test)]
{
SEEN_PTRS.with(|hm| {
let mut hmap = hm.borrow_mut();
{
if let Some(len) = hmap.get(&base_ptr) {
let size = *(base_ptr as *mut usize);
assert_eq!(*len, size);
}
}
hmap.remove(&base_ptr);
});
}
let size = *(base_ptr as *mut usize);
unmap(base_ptr, size);
}
pub unsafe fn get_commitment(item: *mut u8) -> (usize, *mut u8) {
let base_ptr = item.offset(-PAGE_SIZE) as *mut usize;
(*base_ptr, base_ptr as *mut u8)
}
}
#[cfg(test)]
mod tests {
extern crate env_logger;
use super::*;
use std::ptr::{write_bytes, write_volatile};
#[test]
fn general_alloc_basic_global_single_threaded() {
let _ = env_logger::init();
for size in ((1 << 13) - 8)..((1 << 13) + 1) {
unsafe {
let item = global::alloc(size * 8);
write_volatile(item, 10);
global::free(item);
}
}
}
#[test]
fn general_alloc_basic_clone_single_threaded() {
let _ = env_logger::init();
let da_c = DynamicAllocator::new();
let mut da = da_c.clone();
for size in ((1 << 13) - 8)..((1 << 13) + 1) {
unsafe {
let item = da.alloc(size * 8);
write_volatile(item, 10);
da.free(item);
}
}
}
#[test]
fn general_alloc_basic_global_many_threads() {
let _ = env_logger::init();
use std::thread;
const N_THREADS: usize = 32;
let mut threads = Vec::with_capacity(N_THREADS);
for t in 0..N_THREADS {
threads.push(thread::Builder::new()
.name(t.to_string())
.spawn(move || {
for size in 1..(1 << 13) {
unsafe {
let item = global::alloc(size * 8);
write_volatile(item, 10);
global::free(item);
}
if size * 8 >= (1 << 20) {
return;
}
}
})
.unwrap());
}
for t in threads {
t.join().expect("threads should exit successfully")
}
}
#[test]
fn general_alloc_large_ws_global_many_threads() {
let _ = env_logger::init();
use std::thread;
const N_THREADS: usize = 32;
let mut threads = Vec::with_capacity(N_THREADS);
for t in 0..N_THREADS {
threads.push(thread::Builder::new()
.name(t.to_string())
.spawn(move || unsafe {
for _ in 0..2 {
let ptrs: Vec<*mut u8> = (0..(1 << 20)).map(|_| global::alloc(8)).collect();
for p in ptrs {
global::free(p);
}
}
})
.unwrap());
}
for t in threads {
t.join().expect("threads should exit successfully")
}
}
#[test]
fn general_alloc_basic_clone_many_threads() {
let _ = env_logger::init();
use std::thread;
const N_THREADS: usize = 32;
let alloc = DynamicAllocator::new();
let mut threads = Vec::with_capacity(N_THREADS);
for t in 0..N_THREADS {
let mut da = alloc.clone();
threads.push(thread::Builder::new()
.name(t.to_string())
.spawn(move || {
for size in 1..(1 << 13) {
unsafe {
let item = da.alloc(size * 8);
write_bytes(item, 0xFF, size * 8);
da.free(item);
}
if size * 8 >= (1 << 20) {
return;
}
}
})
.unwrap());
}
for t in threads {
t.join().expect("threads should exit successfully")
}
}
#[test]
fn all_sizes_one_thread() {
let _ = env_logger::init();
for size in 1..((1 << 21) + 1) {
unsafe {
let item = global::alloc(size);
write_volatile(item, 10);
global::free(item);
if size + 2 > 1 << 20 {
return;
}
}
}
}
}