use core::ptr::NonNull;
use core::sync::atomic::{AtomicU64, Ordering::Relaxed};
use crate::os;
use crate::stats::Stats;
pub(crate) const POOL_SLOTS: usize = 32;
static POOLED: AtomicU64 = AtomicU64::new(0);
struct PoolInner {
entries: [(usize, usize, u8); POOL_SLOTS], len: u8,
generation: u8,
}
static POOL_LOCK: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
static mut POOL: PoolInner =
PoolInner { entries: [(0, 0, 0); POOL_SLOTS], len: 0, generation: 0 };
fn with_pool<R>(f: impl FnOnce(&mut PoolInner) -> R) -> R {
use core::sync::atomic::Ordering;
while POOL_LOCK
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
core::hint::spin_loop();
}
let out = f(unsafe { &mut *core::ptr::addr_of_mut!(POOL) });
POOL_LOCK.store(false, Ordering::Release);
out
}
fn pool_take(mapped: usize) -> Option<NonNull<u8>> {
with_pool(|p| {
for i in 0..p.len as usize {
if p.entries[i].1 == mapped {
let (addr, _, _) = p.entries[i];
p.len -= 1;
p.entries[i] = p.entries[p.len as usize];
POOLED.fetch_sub(mapped as u64, Relaxed);
return NonNull::new(addr as *mut u8);
}
}
None
})
}
const POOL_MAX_LEN: usize = 1 << 20;
fn pool_park(ptr: NonNull<u8>, mapped: usize) -> bool {
with_pool(|p| {
if p.len as usize == POOL_SLOTS || mapped > POOL_MAX_LEN {
return false;
}
p.entries[p.len as usize] = (ptr.as_ptr() as usize, mapped, p.generation);
p.len += 1;
POOLED.fetch_add(mapped as u64, Relaxed);
true
})
}
pub(crate) fn pool_drain() {
const POOL_AGE_DRAINS: u8 = 64;
let mut held: [(usize, usize); POOL_SLOTS] = [(0, 0); POOL_SLOTS];
let n = with_pool(|p| {
p.generation = p.generation.wrapping_add(1);
let mut n = 0usize;
let mut i = 0usize;
while i < p.len as usize {
let (addr, mapped, born) = p.entries[i];
if p.generation.wrapping_sub(born) >= POOL_AGE_DRAINS {
held[n] = (addr, mapped);
n += 1;
p.len -= 1;
p.entries[i] = p.entries[p.len as usize];
} else {
i += 1;
}
}
n
});
for &(addr, mapped) in &held[..n] {
POOLED.fetch_sub(mapped as u64, Relaxed);
counters::sub_mapped_only(mapped as u64);
unsafe {
os::unmap(NonNull::new_unchecked(addr as *mut u8), mapped);
}
}
}
mod counters {
use core::sync::atomic::{AtomicU64, Ordering::Relaxed};
pub(super) static MAPPED: AtomicU64 = AtomicU64::new(0);
pub(super) static LIVE: AtomicU64 = AtomicU64::new(0);
pub(super) static ROUNDING: AtomicU64 = AtomicU64::new(0);
pub(super) static COUNT: AtomicU64 = AtomicU64::new(0);
pub(super) fn add(mapped: u64, requested: u64) {
MAPPED.fetch_add(mapped, Relaxed);
add_live_only(mapped, requested);
}
pub(super) fn add_live_only(mapped: u64, requested: u64) {
LIVE.fetch_add(requested, Relaxed);
ROUNDING.fetch_add(mapped - requested, Relaxed);
COUNT.fetch_add(1, Relaxed);
}
pub(super) fn sub_live_only(mapped: u64, requested: u64) {
LIVE.fetch_sub(requested, Relaxed);
ROUNDING.fetch_sub(mapped - requested, Relaxed);
COUNT.fetch_sub(1, Relaxed);
}
pub(super) fn sub_mapped_only(mapped: u64) {
MAPPED.fetch_sub(mapped, Relaxed);
}
}
#[must_use]
pub fn large_stats() -> Stats {
use core::sync::atomic::Ordering::Relaxed;
Stats {
mapped: counters::MAPPED.load(Relaxed),
live: counters::LIVE.load(Relaxed),
rounding: counters::ROUNDING.load(Relaxed),
hysteresis: POOLED.load(Relaxed),
large_count: counters::COUNT.load(Relaxed),
..Stats::default()
}
}
pub(crate) fn alloc(size: usize, align: usize) -> Option<NonNull<u8>> {
if align > os::PAGE {
return None;
}
let mapped = os::round_up(size, os::PAGE);
if let Some(p) = pool_take(mapped) {
counters::add_live_only(mapped as u64, size as u64);
return Some(p);
}
let p = os::map_aligned(mapped, os::PAGE)?;
counters::add(mapped as u64, size as u64);
Some(p)
}
pub(crate) unsafe fn dealloc(ptr: NonNull<u8>, size: usize) {
let mapped = os::round_up(size, os::PAGE);
counters::sub_live_only(mapped as u64, size as u64);
if pool_park(ptr, mapped) {
return;
}
counters::sub_mapped_only(mapped as u64);
unsafe { os::unmap(ptr, mapped) };
}
#[cfg(test)]
fn parked_count(mapped: usize) -> usize {
with_pool(|p| (0..p.len as usize).filter(|&i| p.entries[i].1 == mapped).count())
}
#[cfg(test)]
mod pool_tests {
use super::*;
#[test]
fn the_pool_holds_a_teardown_burst_and_refuses_any_giant() {
let size = crate::class::MAX_SMALL + 98_765;
let mapped = os::round_up(size, os::PAGE);
let blocks: Vec<NonNull<u8>> =
(0..8).map(|_| alloc(size, 8).expect("direct mapping")).collect();
for p in blocks {
unsafe { dealloc(p, size) };
}
assert_eq!(parked_count(mapped), 8, "the burst should park whole");
let again: Vec<NonNull<u8>> =
(0..8).map(|_| alloc(size, 8).expect("retake")).collect();
assert_eq!(parked_count(mapped), 0, "a parked mapping was not re-taken");
for p in again {
unsafe { dealloc(p, size) };
}
let giant = POOL_MAX_LEN + 1;
let p = alloc(giant, 8).expect("map 1 MiB + a byte");
unsafe { dealloc(p, giant) };
assert_eq!(
parked_count(os::round_up(giant, os::PAGE)),
0,
"an oversized mapping must never park"
);
}
}