use core::ptr;
use core::sync::atomic::{AtomicPtr, AtomicU64, AtomicUsize, Ordering};
use crate::os;
use crate::types::SEGMENT_SIZE;
const MAX_ARENAS: usize = 32;
const MAX_CHUNKS: usize = 1024;
pub struct Arena {
pub base: *mut u8,
pub size: usize,
pub chunks: usize,
pub exclusive: bool,
pub owned: bool,
pub numa_node: i32,
used: [AtomicU64; MAX_CHUNKS / 64],
dirty: [AtomicU64; MAX_CHUNKS / 64],
}
static ARENAS: [AtomicPtr<Arena>; MAX_ARENAS] =
[const { AtomicPtr::new(ptr::null_mut()) }; MAX_ARENAS];
static ARENA_COUNT: AtomicUsize = AtomicUsize::new(0);
fn arena_register(
base: *mut u8,
size: usize,
exclusive: bool,
owned: bool,
numa_node: i32,
) -> Result<i32, ()> {
let chunks = size / SEGMENT_SIZE;
if chunks == 0 || chunks > MAX_CHUNKS || !base.addr().is_multiple_of(SEGMENT_SIZE) {
return Err(());
}
let desc = os::alloc_aligned(core::mem::size_of::<Arena>(), os::page_size(), true, false)
.map_err(|_| ())?;
let a: *mut Arena = desc.ptr.cast();
unsafe {
(*a).base = base;
(*a).size = size;
(*a).chunks = chunks;
(*a).exclusive = exclusive;
(*a).owned = owned;
(*a).numa_node = numa_node;
}
let id = ARENA_COUNT.fetch_add(1, Ordering::AcqRel);
if id >= MAX_ARENAS {
ARENA_COUNT.fetch_sub(1, Ordering::AcqRel);
return Err(());
}
ARENAS[id].store(a, Ordering::Release);
Ok(id as i32)
}
#[allow(clippy::result_unit_err)] pub fn reserve_os_memory_ex(
size: usize,
_commit: bool,
allow_large: bool,
exclusive: bool,
) -> Result<i32, ()> {
let total = size.div_ceil(SEGMENT_SIZE) * SEGMENT_SIZE;
let b = os::alloc_aligned(total, SEGMENT_SIZE, true, allow_large).map_err(|_| ())?;
arena_register(b.ptr, total, exclusive, true, -1)
}
#[allow(clippy::result_unit_err)] pub fn manage_os_memory_ex(
start: *mut u8,
size: usize,
_is_committed: bool,
_is_large: bool,
_is_zero: bool,
numa_node: i32,
exclusive: bool,
) -> Result<i32, ()> {
let lo_addr = (start.addr() + SEGMENT_SIZE - 1) & !(SEGMENT_SIZE - 1);
let hi = (start.addr() + size) & !(SEGMENT_SIZE - 1);
if hi <= lo_addr {
return Err(());
}
let lo = start.with_addr(lo_addr);
let id = arena_register(lo, hi - lo_addr, exclusive, false, numa_node)?;
let a = ARENAS[id as usize].load(Ordering::Acquire);
unsafe {
let chunks = (*a).chunks;
for w in 0..chunks.div_ceil(64) {
let bits = if (w + 1) * 64 <= chunks {
u64::MAX
} else {
(1u64 << (chunks % 64)) - 1
};
(*a).dirty[w].store(bits, Ordering::Relaxed);
}
}
Ok(id)
}
const DEFAULT_ARENA_PAYS: bool = !cfg!(all(target_arch = "wasm32", not(miri)));
fn ensure_default_arena() {
use core::sync::atomic::AtomicBool;
static TRIED: AtomicBool = AtomicBool::new(false);
if TRIED.swap(true, Ordering::AcqRel) {
return;
}
if !DEFAULT_ARENA_PAYS {
return;
}
let reserve = crate::options::get_size(23); if reserve >= SEGMENT_SIZE {
let _ = reserve_os_memory_ex(reserve, true, false, false);
}
}
pub fn chunk_alloc(restrict_id: i32) -> Option<(*mut u8, bool)> {
if restrict_id < 0 {
if crate::options::is_enabled(27) {
return None; }
ensure_default_arena();
}
chunk_alloc_inner(restrict_id)
}
#[allow(clippy::needless_range_loop)] fn chunk_alloc_inner(restrict_id: i32) -> Option<(*mut u8, bool)> {
let n = ARENA_COUNT.load(Ordering::Acquire).min(MAX_ARENAS);
for id in 0..n {
if restrict_id >= 0 && id != restrict_id as usize {
continue;
}
let a = ARENAS[id].load(Ordering::Acquire);
if a.is_null() {
continue;
}
unsafe {
if restrict_id < 0 && (*a).exclusive {
continue;
}
let words = (*a).chunks.div_ceil(64);
for w in 0..words {
loop {
let cur = (*a).used[w].load(Ordering::Acquire);
let limit = if (w + 1) * 64 <= (*a).chunks {
64
} else {
(*a).chunks % 64
};
let free_bits = !cur
& if limit == 64 {
u64::MAX
} else {
(1u64 << limit) - 1
};
if free_bits == 0 {
break;
}
let bit = free_bits.trailing_zeros() as usize;
if (*a).used[w]
.compare_exchange_weak(
cur,
cur | (1 << bit),
Ordering::AcqRel,
Ordering::Acquire,
)
.is_err()
{
continue;
}
let idx = w * 64 + bit;
let was_dirty =
(*a).dirty[w].fetch_or(1 << bit, Ordering::AcqRel) & (1 << bit) != 0;
let p = (*a).base.add(idx * SEGMENT_SIZE);
return Some((p, !was_dirty));
}
}
}
}
None
}
static MULTI_LOCK: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
#[allow(clippy::needless_range_loop)] pub fn chunk_alloc_n(restrict_id: i32, n: usize) -> Option<(*mut u8, bool)> {
if n == 1 {
return chunk_alloc(restrict_id);
}
if restrict_id < 0 {
if crate::options::is_enabled(27) {
return None;
}
ensure_default_arena();
}
while MULTI_LOCK
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
core::hint::spin_loop();
}
let result = (|| {
let count = ARENA_COUNT.load(Ordering::Acquire).min(MAX_ARENAS);
for id in 0..count {
if restrict_id >= 0 && id != restrict_id as usize {
continue;
}
let a = ARENAS[id].load(Ordering::Acquire);
if a.is_null() {
continue;
}
unsafe {
if restrict_id < 0 && (*a).exclusive {
continue;
}
let chunks = (*a).chunks;
if n > chunks {
continue;
}
let mut run = 0usize;
let mut idx = 0usize;
while idx < chunks {
let bit = (*a).used[idx / 64].load(Ordering::Acquire) & (1 << (idx % 64));
run = if bit == 0 { run + 1 } else { 0 };
if run == n {
let start = idx + 1 - n;
let mut conflict = None;
for j in start..=idx {
let prev = (*a).used[j / 64].fetch_or(1 << (j % 64), Ordering::AcqRel);
if prev & (1 << (j % 64)) != 0 {
conflict = Some(j);
break;
}
}
if let Some(c) = conflict {
for j in start..c {
(*a).used[j / 64].fetch_and(!(1 << (j % 64)), Ordering::AcqRel);
}
run = 0;
idx = c + 1;
continue;
}
let mut any_dirty = false;
for j in start..=idx {
any_dirty |= (*a).dirty[j / 64]
.fetch_or(1 << (j % 64), Ordering::AcqRel)
& (1 << (j % 64))
!= 0;
}
let p = (*a).base.add(start * SEGMENT_SIZE);
return Some((p, !any_dirty));
}
idx += 1;
}
}
}
None
})();
MULTI_LOCK.store(false, Ordering::Release);
result
}
#[allow(clippy::needless_range_loop)] pub fn chunk_free_n(p: *mut u8, n: usize) -> bool {
let addr = p.addr();
let count = ARENA_COUNT.load(Ordering::Acquire).min(MAX_ARENAS);
for id in 0..count {
let a = ARENAS[id].load(Ordering::Acquire);
if a.is_null() {
continue;
}
unsafe {
if addr >= (*a).base.addr() && addr < (*a).base.addr() + (*a).size {
let start = (addr - (*a).base.addr()) / SEGMENT_SIZE;
for j in start..start + n {
(*a).used[j / 64].fetch_and(!(1 << (j % 64)), Ordering::AcqRel);
}
return true;
}
}
}
false
}
#[allow(clippy::needless_range_loop)] pub fn chunk_free(p: *mut u8) -> bool {
let addr = p.addr();
let n = ARENA_COUNT.load(Ordering::Acquire).min(MAX_ARENAS);
for id in 0..n {
let a = ARENAS[id].load(Ordering::Acquire);
if a.is_null() {
continue;
}
unsafe {
if addr >= (*a).base.addr() && addr < (*a).base.addr() + (*a).size {
let idx = (addr - (*a).base.addr()) / SEGMENT_SIZE;
(*a).used[idx / 64].fetch_and(!(1 << (idx % 64)), Ordering::AcqRel);
return true;
}
}
}
false
}
pub fn arena_area(id: i32) -> (*mut u8, usize) {
if id < 0 || id as usize >= ARENA_COUNT.load(Ordering::Acquire) {
return (ptr::null_mut(), 0);
}
let a = ARENAS[id as usize].load(Ordering::Acquire);
if a.is_null() {
return (ptr::null_mut(), 0);
}
unsafe { ((*a).base, (*a).size) }
}
#[allow(clippy::needless_range_loop)] pub fn arenas_print(out: &mut dyn FnMut(&str)) {
let n = ARENA_COUNT.load(Ordering::Acquire).min(MAX_ARENAS);
if n == 0 {
out("arenas: none\n");
return;
}
for id in 0..n {
let a = ARENAS[id].load(Ordering::Acquire);
if a.is_null() {
continue;
}
unsafe {
let mut used = 0usize;
for w in 0..(*a).chunks.div_ceil(64) {
used += (*a).used[w].load(Ordering::Relaxed).count_ones() as usize;
}
let mut line = heapless_fmt(
id,
(*a).base.addr(),
(*a).size,
used,
(*a).chunks,
(*a).exclusive,
);
out(line.as_str());
line.clear();
}
}
}
fn heapless_fmt(
id: usize,
base: usize,
size: usize,
used: usize,
chunks: usize,
excl: bool,
) -> String {
format!(
"arena {id}: base {base:#x} size {} MiB, {used}/{chunks} chunks used{}\n",
size / (1024 * 1024),
if excl { " (exclusive)" } else { "" }
)
}