use crate::arena::Arena;
use crate::os;
use crate::prof::Sample;
use crate::size_class::{PAGE, PAGE_SHIFT};
use crate::stats;
use core::ptr::null_mut;
pub const CHUNK: usize = 1 << 22;
pub const PAGES: usize = CHUNK >> PAGE_SHIFT;
const WORDS: usize = PAGES / 64;
pub const KIND_ARENA: u8 = 1;
pub const KIND_HUGE: u8 = 2;
pub const RUN_FREE: u8 = 0;
pub const RUN_SLAB: u8 = 1;
pub const RUN_LARGE: u8 = 2;
#[repr(C)]
pub struct ChunkHead {
pub kind: u8,
pub sampled: u8,
}
#[inline(always)]
pub fn head_of(ptr: *mut u8) -> *mut ChunkHead {
core::ptr::with_exposed_provenance_mut((ptr.addr() - 1) & !(CHUNK - 1))
}
#[inline(always)]
pub fn owned(ptr: *mut u8) -> *mut u8 {
core::ptr::with_exposed_provenance_mut(ptr.expose_provenance())
}
#[repr(C)]
pub struct Run {
pub free_list: *mut u8,
pub next: *mut Run,
pub prev: *mut Run,
pub sample: *mut Sample,
pub npages: u16,
pub nfree: u16,
pub bump: u16,
pub class: u8,
pub state: u8,
}
pub type Bitmap = [u64; WORDS];
#[repr(C)]
pub struct ArenaChunk {
pub head: ChunkHead,
pub nfree: u32,
pub arena: *const Arena,
pub next: *mut ArenaChunk,
pub spare_epoch: u64,
pub free: Bitmap,
pub dirty_new: Bitmap,
pub dirty_old: Bitmap,
pub run_start: [u16; PAGES],
pub runs: [Run; PAGES],
}
pub const HEADER_PAGES: usize = size_of::<ArenaChunk>().div_ceil(PAGE);
pub const USABLE_PAGES: usize = PAGES - HEADER_PAGES;
impl ArenaChunk {
#[inline(always)]
pub fn of(ptr: *mut u8) -> *mut ArenaChunk {
head_of(ptr).cast()
}
#[inline(always)]
pub fn page_of(c: *mut ArenaChunk, ptr: *mut u8) -> usize {
(ptr.addr() - c.addr()) >> PAGE_SHIFT
}
#[inline(always)]
pub unsafe fn page_addr(c: *mut ArenaChunk, page: usize) -> *mut u8 {
c.cast::<u8>().add(page << PAGE_SHIFT)
}
#[inline(always)]
pub unsafe fn run(c: *mut ArenaChunk, p: usize) -> *mut Run {
(&raw mut (*c).runs).cast::<Run>().add(p)
}
#[inline(always)]
pub unsafe fn run_page(c: *mut ArenaChunk, run: *const Run) -> usize {
(run.addr() - Self::run(c, 0).addr()) / size_of::<Run>()
}
#[inline(always)]
pub unsafe fn run_start(c: *mut ArenaChunk, p: usize) -> usize {
*(&raw const (*c).run_start).cast::<u16>().add(p) as usize
}
#[inline(always)]
pub unsafe fn set_run_start(c: *mut ArenaChunk, p: usize, start: usize) {
*(&raw mut (*c).run_start).cast::<u16>().add(p) = start as u16;
}
#[inline(always)]
pub unsafe fn run_of(c: *mut ArenaChunk, ptr: *mut u8) -> (usize, *mut Run) {
let p = Self::run_start(c, Self::page_of(c, ptr));
(p, Self::run(c, p))
}
pub unsafe fn create(arena: *const Arena, sampled: bool) -> *mut ArenaChunk {
let c: *mut ArenaChunk = crate::arena::map_or_make_room(CHUNK, CHUNK).cast();
if c.is_null() {
return c;
}
c.expose_provenance();
os::no_huge_pages(c.cast(), CHUNK);
stats::CHUNK_BYTES.add(CHUNK);
(*c).head.kind = KIND_ARENA;
(*c).head.sampled = u8::from(sampled);
(*c).arena = arena;
(*c).next = null_mut();
(*c).nfree = USABLE_PAGES as u32;
set_range(&mut (*c).free, HEADER_PAGES, USABLE_PAGES);
c
}
#[inline]
pub unsafe fn is_free_page(c: *mut ArenaChunk, p: usize) -> bool {
(*c).free[p / 64] & (1 << (p % 64)) != 0
}
pub unsafe fn take(c: *mut ArenaChunk, start: usize, p: usize, n: usize) {
clear_range(&mut (*c).free, p, n);
clear_range(&mut (*c).dirty_new, p, n);
clear_range(&mut (*c).dirty_old, p, n);
(*c).nfree -= n as u32;
for i in p..p + n {
Self::set_run_start(c, i, start);
}
}
pub unsafe fn give_back(c: *mut ArenaChunk, p: usize, n: usize) {
set_range(&mut (*c).free, p, n);
set_range(&mut (*c).dirty_new, p, n);
(*c).nfree += n as u32;
}
pub unsafe fn is_empty(c: *mut ArenaChunk) -> bool {
(*c).nfree as usize == USABLE_PAGES
}
pub unsafe fn decay(c: *mut ArenaChunk, all: bool) {
let os_pages = (os::page_size() >> PAGE_SHIFT).max(1);
let purged = decay_bits(
&(*c).free,
&mut (*c).dirty_old,
&mut (*c).dirty_new,
all,
os_pages,
);
let mut i = 0;
loop {
let s = next_set(&purged, i);
if s >= PAGES {
break;
}
let e = next_clear(&purged, s);
os::purge(Self::page_addr(c, s), (e - s) << PAGE_SHIFT);
i = e;
}
}
}
fn decay_bits(
free: &Bitmap,
old: &mut Bitmap,
new: &mut Bitmap,
all: bool,
os_pages: usize,
) -> Bitmap {
let mut purged: Bitmap = [0; WORDS];
let mut p = 0;
while p < PAGES {
let n = os_pages.min(PAGES - p);
let due = (p..p + n).any(|i| {
let bit = 1 << (i % 64);
old[i / 64] & bit != 0 || (all && new[i / 64] & bit != 0)
});
let all_free = (p..p + n).all(|i| free[i / 64] & (1 << (i % 64)) != 0);
if due && all_free {
set_range(&mut purged, p, n);
clear_range(old, p, n);
clear_range(new, p, n);
}
p += n;
}
for w in 0..WORDS {
old[w] |= new[w];
new[w] = 0;
}
purged
}
fn update_range(bm: &mut Bitmap, start: usize, n: usize, set: bool) {
let end = start + n;
let mut i = start;
while i < end {
let bit = i % 64;
let cnt = (64 - bit).min(end - i);
let mask = if cnt == 64 {
!0
} else {
((1u64 << cnt) - 1) << bit
};
if set {
bm[i / 64] |= mask;
} else {
bm[i / 64] &= !mask;
}
i += cnt;
}
}
fn set_range(bm: &mut Bitmap, start: usize, n: usize) {
update_range(bm, start, n, true);
}
fn clear_range(bm: &mut Bitmap, start: usize, n: usize) {
update_range(bm, start, n, false);
}
#[inline]
fn next_bit(bm: &Bitmap, from: usize, want: bool) -> usize {
let mut w = from / 64;
if w >= WORDS {
return PAGES;
}
let flip = if want { 0 } else { !0 };
let mut bits = (bm[w] ^ flip) & (!0u64 << (from % 64));
loop {
if bits != 0 {
return w * 64 + bits.trailing_zeros() as usize;
}
w += 1;
if w == WORDS {
return PAGES;
}
bits = bm[w] ^ flip;
}
}
fn next_set(bm: &Bitmap, from: usize) -> usize {
next_bit(bm, from, true)
}
fn next_clear(bm: &Bitmap, from: usize) -> usize {
next_bit(bm, from, false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bitmap_runs() {
let mut bm: Bitmap = [0; WORDS];
set_range(&mut bm, 10, 100);
assert_eq!(next_set(&bm, 0), 10);
assert_eq!(next_clear(&bm, 10), 110);
clear_range(&mut bm, 60, 10);
assert_eq!(next_clear(&bm, 10), 60);
assert_eq!(next_set(&bm, 60), 70);
set_range(&mut bm, 0, PAGES);
assert_eq!(next_clear(&bm, 0), PAGES);
}
#[test]
fn decay_waits_for_whole_os_pages() {
let (mut free, mut old, mut new): (Bitmap, Bitmap, Bitmap) =
([0; WORDS], [0; WORDS], [0; WORDS]);
let os_pages = 4;
set_range(&mut free, 16, 1);
set_range(&mut new, 16, 1);
let purged = decay_bits(&free, &mut old, &mut new, false, os_pages);
assert_eq!(next_set(&purged, 0), PAGES, "nothing is due yet");
let purged = decay_bits(&free, &mut old, &mut new, false, os_pages);
assert_eq!(next_set(&purged, 0), PAGES, "the OS page is not all free");
assert_eq!(next_set(&old, 0), 16, "page 16 stays due");
set_range(&mut free, 17, 3);
set_range(&mut new, 17, 3);
let purged = decay_bits(&free, &mut old, &mut new, false, os_pages);
assert_eq!((next_set(&purged, 0), next_clear(&purged, 16)), (16, 20));
assert_eq!(next_set(&old, 0), PAGES);
assert_eq!(next_set(&new, 0), PAGES);
set_range(&mut free, 64, 8);
set_range(&mut new, 64, 8);
let purged = decay_bits(&free, &mut old, &mut new, true, os_pages);
assert_eq!((next_set(&purged, 0), next_clear(&purged, 64)), (64, 72));
}
#[test]
fn header_fits() {
const { assert!(HEADER_PAGES < 16) };
}
}