use core::cell::Cell;
use core::ptr;
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use crate::init::{self, HeapBox};
use crate::types::SMALL_OBJ_SIZE_MAX;
pub const SHARED_TID: usize = usize::MAX;
pub const SHARED_LIGHT_BYTES: usize = 64 * 1024;
pub const SHARED_MAX_SIZE: usize = SMALL_OBJ_SIZE_MAX;
static SHARED_BOX: AtomicPtr<HeapBox> = AtomicPtr::new(ptr::null_mut());
static SHARED_LOCK: AtomicBool = AtomicBool::new(false);
static FIRST_CLAIMED: AtomicBool = AtomicBool::new(false);
ra_thread_local! {
static LIGHT_BYTES: Cell<usize> = const { Cell::new(0) };
static IN_SHARED: Cell<bool> = const { Cell::new(false) };
}
#[inline]
fn in_shared() -> bool {
IN_SHARED.with(Cell::get)
}
#[inline]
pub(crate) unsafe fn stamp_if_shared(heap: &crate::heap::Heap, seg: *mut crate::segment::Segment) {
let hb = SHARED_BOX.load(Ordering::Relaxed);
if !hb.is_null() && ptr::eq(unsafe { (*hb).heap.get() }.cast_const(), heap) {
unsafe { (*seg).thread_id.store(SHARED_TID, Ordering::Release) };
}
}
const SPIN_LIMIT: u32 = 128;
fn lock_briefly() -> bool {
for _ in 0..SPIN_LIMIT {
if !SHARED_LOCK.load(Ordering::Relaxed)
&& SHARED_LOCK
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
return true;
}
core::hint::spin_loop();
}
false
}
fn lock() {
while !lock_briefly() {
#[cfg(feature = "std")]
std::thread::yield_now();
}
}
fn try_lock() -> bool {
SHARED_LOCK
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
fn unlock() {
SHARED_LOCK.store(false, Ordering::Release);
}
unsafe fn with_heap_locked<R>(f: impl FnOnce(&mut crate::heap::Heap) -> R) -> Option<R> {
let mut hb = SHARED_BOX.load(Ordering::Relaxed);
if hb.is_null() {
hb = init::create_shared_heap_box();
if hb.is_null() {
return None;
}
SHARED_BOX.store(hb, Ordering::Relaxed);
}
IN_SHARED.with(|c| c.set(true));
let r = f(unsafe { &mut *(*hb).heap.get() });
IN_SHARED.with(|c| c.set(false));
Some(r)
}
pub(crate) fn try_alloc(size: usize, zero: bool) -> Option<*mut u8> {
if !crate::SHARED_LIGHT || size > SHARED_MAX_SIZE {
return None;
}
if in_shared() || !init::in_default_subproc() {
return None;
}
if !FIRST_CLAIMED.load(Ordering::Relaxed) && !FIRST_CLAIMED.swap(true, Ordering::Relaxed) {
return None;
}
let used = LIGHT_BYTES.with(Cell::get);
if used.saturating_add(size) > SHARED_LIGHT_BYTES {
return None;
}
if !lock_briefly() {
return None;
}
let served = unsafe {
with_heap_locked(|h| {
let p = if zero {
h.zalloc(size)
} else {
h.malloc(size).0
};
let due = core::mem::take(&mut h.deferred_due);
(p, due)
})
};
unlock();
let (p, due) = served?;
if due {
crate::options::deferred_free(false);
}
if p.is_null() {
return None;
}
LIGHT_BYTES.with(|c| c.set(used + size));
Some(p)
}
pub(crate) fn collect(force: bool) -> bool {
if !crate::SHARED_LIGHT || in_shared() {
return false;
}
if SHARED_BOX.load(Ordering::Relaxed).is_null() {
return true; }
if !try_lock() {
return true; }
let _ = unsafe { with_heap_locked(|h| h.collect(force)) };
unlock();
true
}
pub fn shared_box() -> *mut HeapBox {
SHARED_BOX.load(Ordering::Acquire)
}
pub fn used_pages() -> Option<usize> {
if shared_box().is_null() {
return None;
}
lock();
let n = unsafe {
with_heap_locked(|h| {
let mut n = 0usize;
let mut s = h.segments;
while !s.is_null() {
n += (*s).used_pages as usize;
s = (*s).next;
}
n
})
};
unlock();
n
}
#[doc(hidden)]
pub fn test_hold_lock() {
lock();
}
#[doc(hidden)]
pub fn test_release_lock() {
unlock();
}