use crate::view::{Alloc, AllocError};
use core::ptr::NonNull;
use core::sync::atomic::{AtomicUsize, Ordering};
const MIN_BLOCK: usize = 64 << 10; const HEADER: usize = 16; const SLOTS: usize = 8; const ALIGN: usize = 16;
static LIMIT: AtomicUsize = AtomicUsize::new(32 << 20);
#[derive(Clone, Copy)]
struct Block {
p: *mut u8, cap: usize,
}
unsafe impl Send for Block {}
struct Cache {
blocks: [Block; SLOTS],
n: usize,
cached: usize,
}
impl Cache {
const fn new() -> Self {
Cache { blocks: [Block { p: core::ptr::null_mut(), cap: 0 }; SLOTS], n: 0, cached: 0 }
}
fn smallest(&self) -> usize {
let mut s = 0;
for i in 1..self.n {
if self.blocks[i].cap < self.blocks[s].cap {
s = i;
}
}
s
}
fn drop_slot(&mut self, i: usize) -> Block {
let b = self.blocks[i];
self.cached -= b.cap;
self.n -= 1;
self.blocks[i] = self.blocks[self.n];
b
}
}
#[cfg(feature = "std")]
mod lock {
use super::Cache;
use std::sync::{Mutex, MutexGuard};
static CACHE: Mutex<Cache> = Mutex::new(Cache::new());
pub fn lock() -> MutexGuard<'static, Cache> {
CACHE.lock().unwrap_or_else(|e| e.into_inner())
}
}
#[cfg(not(feature = "std"))]
mod lock {
use super::Cache;
use core::cell::UnsafeCell;
use core::sync::atomic::{AtomicBool, Ordering};
struct Spin {
held: AtomicBool,
cache: UnsafeCell<Cache>,
}
unsafe impl Sync for Spin {}
static CACHE: Spin = Spin { held: AtomicBool::new(false), cache: UnsafeCell::new(Cache::new()) };
pub(super) struct Guard;
impl core::ops::Deref for Guard {
type Target = Cache;
fn deref(&self) -> &Cache {
unsafe { &*CACHE.cache.get() }
}
}
impl core::ops::DerefMut for Guard {
fn deref_mut(&mut self) -> &mut Cache {
unsafe { &mut *CACHE.cache.get() }
}
}
impl Drop for Guard {
fn drop(&mut self) {
CACHE.held.store(false, Ordering::Release);
}
}
pub fn lock() -> Guard {
while CACHE.held.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {
core::hint::spin_loop();
}
Guard
}
}
#[inline]
fn raw_alloc(bytes: usize) -> Result<NonNull<u8>, AllocError> {
let layout = core::alloc::Layout::from_size_align(bytes.max(1), ALIGN).map_err(|_| AllocError)?;
NonNull::new(unsafe { alloc::alloc::alloc(layout) }).ok_or(AllocError)
}
#[inline]
unsafe fn raw_free(p: NonNull<u8>, bytes: usize) {
unsafe { alloc::alloc::dealloc(p.as_ptr(), core::alloc::Layout::from_size_align_unchecked(bytes.max(1), ALIGN)) }
}
unsafe fn release_block(b: Block) {
unsafe { raw_free(NonNull::new_unchecked(b.p.sub(HEADER)), b.cap + HEADER) }
}
#[derive(Clone, Copy, Debug, Default)]
pub struct DefaultAlloc;
impl Alloc for DefaultAlloc {
fn allocate(bytes: usize) -> Result<NonNull<u8>, AllocError> {
if bytes < MIN_BLOCK {
return raw_alloc(bytes);
}
{
let mut c = lock::lock();
let mut best = usize::MAX;
for i in 0..c.n {
if c.blocks[i].cap >= bytes && (best == usize::MAX || c.blocks[i].cap < c.blocks[best].cap) {
best = i;
}
}
if best != usize::MAX {
let b = c.drop_slot_keep(best);
return Ok(unsafe { NonNull::new_unchecked(b.p) });
}
}
let raw = raw_alloc(bytes.checked_add(HEADER).ok_or(AllocError)?)?;
unsafe {
core::ptr::write_unaligned(raw.as_ptr() as *mut usize, bytes);
Ok(NonNull::new_unchecked(raw.as_ptr().add(HEADER)))
}
}
unsafe fn deallocate(p: NonNull<u8>, bytes: usize) {
if bytes < MIN_BLOCK {
return unsafe { raw_free(p, bytes) };
}
let cap = unsafe { core::ptr::read_unaligned(p.as_ptr().sub(HEADER) as *const usize) };
let me = Block { p: p.as_ptr(), cap };
let limit = LIMIT.load(Ordering::Relaxed);
let mut victims: [Option<Block>; SLOTS + 1] = [None; SLOTS + 1];
let mut nv = 0;
{
let mut c = lock::lock();
if cap > limit {
victims[nv] = Some(me);
nv += 1;
} else {
while c.n > 0 && c.cached + cap > limit {
let s = c.smallest();
victims[nv] = Some(c.drop_slot(s));
nv += 1;
}
let mut keep = true;
if c.n == SLOTS {
let s = c.smallest();
if c.blocks[s].cap >= cap {
keep = false;
victims[nv] = Some(me);
nv += 1;
} else {
victims[nv] = Some(c.drop_slot(s));
nv += 1;
}
}
if keep {
let n = c.n;
c.blocks[n] = me;
c.n += 1;
c.cached += cap;
}
}
}
for v in victims.iter().take(nv).flatten() {
unsafe { release_block(*v) };
}
}
}
impl Cache {
fn drop_slot_keep(&mut self, i: usize) -> Block {
self.drop_slot(i)
}
}
pub fn release_memory() {
let mut held: [Option<Block>; SLOTS] = [None; SLOTS];
let n;
{
let mut c = lock::lock();
n = c.n;
for (i, slot) in held.iter_mut().enumerate().take(n) {
*slot = Some(c.blocks[i]);
}
c.n = 0;
c.cached = 0;
}
for b in held.iter().take(n).flatten() {
unsafe { release_block(*b) };
}
}
pub fn set_memory_cache_limit(bytes: usize) {
LIMIT.store(bytes, Ordering::Relaxed);
}