use crate::arena::{self, Arena};
use crate::base::Pool;
use crate::prof;
use crate::size_class::{CACHE_CAP, NCACHED, NSMALL, TOTAL_SLOTS};
use core::cell::Cell;
use core::ptr::{self, null_mut};
use core::sync::atomic::Ordering;
const GC_INTERVAL: i64 = 64 << 10;
const PROF_CHECK_INTERVAL: i64 = 1 << 20;
#[repr(C)]
pub struct CacheBin {
pub slots: *mut *mut u8,
pub n: u32,
pub cap: u32,
pub low_water: u32,
}
#[repr(C)]
pub struct TCache {
pub until_event: i64,
pub bins: [CacheBin; NCACHED],
pub arena: *mut Arena,
event_start: i64,
sample_wait: i64,
gc_wait: i64,
sampling: bool,
interval: usize,
gc_bin: usize,
rng: u64,
}
#[repr(C)]
struct Block {
cache: TCache,
slots: [*mut u8; TOTAL_SLOTS],
}
static POOL: Pool<Block> = Pool::new();
const INITIALIZING: usize = 1;
const DEAD: usize = 2;
fn state(s: usize) -> *mut TCache {
ptr::without_provenance_mut(s)
}
thread_local! {
static CURRENT: Cell<*mut TCache> = const { Cell::new(null_mut()) };
static GUARD: Guard = const { Guard };
}
struct Guard;
impl Drop for Guard {
fn drop(&mut self) {
let t = CURRENT.with(|c| c.replace(state(DEAD)));
if t.addr() > DEAD {
unsafe {
flush_all(&mut *t);
arena::leave((*t).arena);
POOL.free(t.cast::<Block>());
}
}
}
}
#[cfg(all(unix, not(miri)))]
pub(crate) fn fork_lock() {
POOL.fork_lock();
}
#[cfg(all(unix, not(miri)))]
pub(crate) unsafe fn fork_unlock() {
POOL.fork_unlock();
}
#[inline(always)]
pub fn current() -> *mut TCache {
let t = CURRENT.with(Cell::get);
if t.addr() > DEAD { t } else { null_mut() }
}
pub unsafe fn get_or_init() -> *mut TCache {
let t = CURRENT.with(Cell::get);
if t.addr() > DEAD {
return t;
}
if !t.is_null() {
return null_mut();
}
CURRENT.with(|c| c.set(state(INITIALIZING)));
if GUARD.try_with(|_| ()).is_err() {
CURRENT.with(|c| c.set(state(DEAD)));
return null_mut();
}
let block = POOL.alloc();
let arena = if block.is_null() {
null_mut()
} else {
arena::choose()
};
if arena.is_null() {
if !block.is_null() {
POOL.free(block);
}
CURRENT.with(|c| c.set(null_mut()));
return null_mut();
}
let t = init(block, arena);
CURRENT.with(|c| c.set(t));
#[cfg(all(unix, not(miri)))]
crate::fork::register();
t
}
unsafe fn init(block: *mut Block, arena: *mut Arena) -> *mut TCache {
let t = &raw mut (*block).cache;
let slots = (&raw mut (*block).slots).cast::<*mut u8>();
let mut offset = 0;
for c in 0..NCACHED {
let cap = u32::from(CACHE_CAP[c]);
ptr::write(
ptr::addr_of_mut!((*t).bins[c]),
CacheBin {
slots: slots.add(offset),
n: 0,
cap,
low_water: 0,
},
);
offset += cap as usize;
}
(*t).arena = arena;
(*t).gc_wait = GC_INTERVAL;
(*t).gc_bin = 0;
(*t).rng = prof::new_seed();
let active = prof::ACTIVE.load(Ordering::Relaxed);
let interval = prof::sample_interval();
(*t).sampling = active;
(*t).interval = interval;
(*t).sample_wait = if active {
prof::next_wait(&mut (*t).rng, interval)
} else {
PROF_CHECK_INTERVAL
};
let next = (*t).sample_wait.min(GC_INTERVAL);
(*t).until_event = next;
(*t).event_start = next;
t
}
#[cold]
pub unsafe fn event(t: &mut TCache, bytes: i64) -> Option<usize> {
let elapsed = t.event_start - t.until_event;
t.sample_wait -= elapsed;
t.gc_wait -= elapsed;
let active = prof::ACTIVE.load(Ordering::Relaxed);
let interval = prof::sample_interval();
let mut sample = None;
if t.sample_wait <= 0 {
if !active {
t.sampling = false;
t.sample_wait = PROF_CHECK_INTERVAL;
} else if t.sampling {
sample = Some(t.interval);
t.interval = interval;
t.sample_wait = prof::next_wait(&mut t.rng, interval);
} else {
t.sampling = true;
t.interval = interval;
let wait = prof::next_wait(&mut t.rng, interval);
if wait <= bytes {
sample = Some(interval);
t.sample_wait = prof::next_wait(&mut t.rng, interval);
} else {
t.sample_wait = wait - bytes;
}
}
} else if active && t.sampling && t.interval != interval {
t.interval = interval;
t.sample_wait = prof::next_wait(&mut t.rng, interval);
}
if t.gc_wait <= 0 {
gc_step(t);
t.gc_wait = GC_INTERVAL;
}
let next = t.sample_wait.min(t.gc_wait);
t.until_event = next;
t.event_start = next;
sample
}
unsafe fn gc_step(t: &mut TCache) {
let c = t.gc_bin;
t.gc_bin = (c + 1) % NCACHED;
let low = t.bins[c].low_water;
if low > 0 {
flush(t, c, (low - low / 4) as usize);
}
t.bins[c].low_water = t.bins[c].n;
if t.gc_bin == 0 {
arena::decay_tick();
}
}
pub unsafe fn alloc(t: &mut TCache, class: usize) -> *mut u8 {
let bin = &mut t.bins[class];
if bin.n == 0 {
if class >= NSMALL {
return (*t.arena).alloc_large(class);
}
let want = (bin.cap / 2).max(1) as usize;
let n = (*t.arena).refill(class, bin.slots, want);
if n == 0 {
return null_mut();
}
core::slice::from_raw_parts_mut(bin.slots, n).reverse();
bin.n = n as u32;
}
bin.n -= 1;
bin.low_water = bin.low_water.min(bin.n);
*bin.slots.add(bin.n as usize)
}
pub unsafe fn free(t: &mut TCache, class: usize, ptr: *mut u8) {
let bin = &t.bins[class];
if bin.n == bin.cap {
flush(t, class, (bin.cap / 2).max(1) as usize);
}
let bin = &mut t.bins[class];
*bin.slots.add(bin.n as usize) = ptr;
bin.n += 1;
}
unsafe fn flush(t: &mut TCache, class: usize, count: usize) {
let bin = &mut t.bins[class];
let count = count.min(bin.n as usize);
if count == 0 {
return;
}
let objs = core::slice::from_raw_parts_mut(bin.slots, count);
if class < NSMALL {
arena::free_small_batch(objs, class);
} else {
for &p in objs.iter() {
arena::free(p);
}
}
ptr::copy(bin.slots.add(count), bin.slots, bin.n as usize - count);
bin.n -= count as u32;
bin.low_water = bin.low_water.min(bin.n);
}
pub unsafe fn flush_all(t: &mut TCache) {
for c in 0..NCACHED {
flush(t, c, t.bins[c].n as usize);
}
}