use std::{
cell::Cell,
ffi::c_void,
sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering},
};
use napi_sys::wasi_heap_sync::{
grow_zero, handoff_refreshes, local_pages, max_seen_pages, needs_refresh, HANDOFF_REFRESHES_STAT,
};
use crate::wasi_heap_break::{fits, grow_plan, pages_below, Grow, HEAP_LIMIT_PAGES, PAGE_BYTES};
#[repr(align(128))]
struct HeapState {
lock: AtomicBool,
brk: AtomicUsize,
own_end: AtomicUsize,
init_pages: AtomicUsize,
}
static STATE: HeapState = HeapState {
lock: AtomicBool::new(false),
brk: AtomicUsize::new(0),
own_end: AtomicUsize::new(0),
init_pages: AtomicUsize::new(0),
};
mod stat {
pub const GROWS: usize = 0;
pub const LOCK_REFRESHES: usize = 1;
pub const LATE_REFRESHES: usize = 2;
pub const BREAK_PAGES: usize = 3;
pub const HEAP_END_PAGES: usize = 4;
pub const COUNT: usize = 5;
}
static STATS: [AtomicU32; stat::COUNT] = [const { AtomicU32::new(0) }; stat::COUNT];
thread_local! {
static IN_LOCKED: Cell<bool> = const { Cell::new(false) };
}
#[inline]
fn bump(index: usize) {
STATS[index].fetch_add(1, Ordering::Relaxed);
}
#[inline]
fn store_pages(index: usize, pages: usize) {
STATS[index].store(u32::try_from(pages).unwrap_or(u32::MAX), Ordering::Relaxed);
}
#[no_mangle]
pub extern "C" fn napi_wasm_heap_sync_stat(index: u32) -> u32 {
if index == HANDOFF_REFRESHES_STAT {
return handoff_refreshes();
}
STATS
.get(index as usize)
.map_or(u32::MAX, |counter| counter.load(Ordering::Relaxed))
}
extern "C" {
fn __real_malloc(size: usize) -> *mut c_void;
fn __real_free(ptr: *mut c_void);
fn __real_calloc(count: usize, size: usize) -> *mut c_void;
fn __real_realloc(ptr: *mut c_void, size: usize) -> *mut c_void;
fn __real_posix_memalign(out: *mut *mut c_void, align: usize, size: usize) -> i32;
fn __real_aligned_alloc(align: usize, size: usize) -> *mut c_void;
fn __real_malloc_usable_size(ptr: *mut c_void) -> usize;
fn __real___libc_malloc(size: usize) -> *mut c_void;
fn __real___libc_free(ptr: *mut c_void);
fn __real___libc_calloc(count: usize, size: usize) -> *mut c_void;
fn sched_yield() -> i32;
static __heap_end: u8;
static napi_wasi_heap_sync_needs_napi_build_setup_with_wasi_heap_sync: u8;
}
#[inline]
fn lock() {
if STATE
.lock
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
lock_slow();
}
}
#[cold]
fn lock_slow() {
let mut spins: u32 = 0;
loop {
while STATE.lock.load(Ordering::Relaxed) {
spins = spins.wrapping_add(1);
if spins.is_multiple_of(64) {
unsafe { sched_yield() };
} else {
core::hint::spin_loop();
}
}
if STATE
.lock
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
return;
}
}
}
#[inline]
fn locked<R>(f: impl FnOnce() -> R) -> R {
lock();
let local = local_pages();
if local == 0 || max_seen_pages() > local {
lock_refresh();
}
IN_LOCKED.with(|in_locked| in_locked.set(true));
let result = f();
IN_LOCKED.with(|in_locked| in_locked.set(false));
STATE.lock.store(false, Ordering::Release);
result
}
#[cold]
fn lock_refresh() {
bump(stat::LOCK_REFRESHES);
grow_zero();
}
#[inline]
fn after(ptr: *mut c_void, size: usize) -> *mut c_void {
if !ptr.is_null() && needs_refresh(ptr as usize as u64 + size as u64, local_pages()) {
late_refresh();
}
ptr
}
#[cold]
fn late_refresh() {
bump(stat::LATE_REFRESHES);
grow_zero();
}
#[no_mangle]
pub unsafe extern "C" fn __wrap_sbrk(increment: isize) -> *mut c_void {
if IN_LOCKED.with(Cell::get) {
unsafe { sbrk_under_lock(increment) }
} else {
locked(|| unsafe { sbrk_under_lock(increment) })
}
}
unsafe fn sbrk_under_lock(increment: isize) -> *mut c_void {
const FAIL: *mut c_void = usize::MAX as *mut c_void;
let mut brk = STATE.brk.load(Ordering::Relaxed);
if brk == 0 {
unsafe {
core::ptr::read_volatile(
&raw const napi_wasi_heap_sync_needs_napi_build_setup_with_wasi_heap_sync,
)
};
let start = pages_below(&raw const __heap_end as usize) * PAGE_BYTES;
if start == 0 {
return FAIL;
}
store_pages(stat::HEAP_END_PAGES, start / PAGE_BYTES);
brk = start;
STATE.brk.store(brk, Ordering::Relaxed);
let now = grow_zero();
let init = STATE.init_pages.load(Ordering::Relaxed);
let reserve_end = if init == 0 { now } else { init.min(now) };
STATE.own_end.store(
reserve_end.min(HEAP_LIMIT_PAGES) * PAGE_BYTES,
Ordering::Relaxed,
);
}
if increment == 0 {
return brk as *mut c_void;
}
let Ok(increment) = usize::try_from(increment) else {
return FAIL;
};
let mut own_end = STATE.own_end.load(Ordering::Relaxed);
if let Some(new_brk) = fits(brk, own_end, increment) {
STATE.brk.store(new_brk, Ordering::Relaxed);
store_pages(stat::BREAK_PAGES, pages_below(new_brk));
return brk as *mut c_void;
}
for _ in 0..2 {
let now = grow_zero();
let Some(Grow { need, ahead }) = grow_plan(brk, own_end, increment, now) else {
return FAIL;
};
let mut grown = ahead;
let mut old = core::arch::wasm32::memory_grow::<0>(ahead);
if old == usize::MAX && ahead != need {
grown = need;
old = core::arch::wasm32::memory_grow::<0>(need);
}
if old == usize::MAX {
return FAIL;
}
bump(stat::GROWS);
grow_zero();
let region = old * PAGE_BYTES;
let end = (old + grown).min(HEAP_LIMIT_PAGES) * PAGE_BYTES;
let base = if region == own_end { brk } else { region };
if let Some(new_brk) = fits(base, end, increment) {
STATE.own_end.store(end, Ordering::Relaxed);
STATE.brk.store(new_brk, Ordering::Relaxed);
store_pages(stat::BREAK_PAGES, pages_below(new_brk));
return base as *mut c_void;
}
if region >= end {
return FAIL;
}
brk = region;
own_end = end;
STATE.brk.store(brk, Ordering::Relaxed);
STATE.own_end.store(own_end, Ordering::Relaxed);
}
FAIL
}
extern "C" fn capture_init_pages() {
let _ = STATE.init_pages.compare_exchange(
0,
core::arch::wasm32::memory_size::<0>(),
Ordering::Relaxed,
Ordering::Relaxed,
);
}
#[used]
#[link_section = ".init_array.00099"]
static CAPTURE_INIT_PAGES: extern "C" fn() = capture_init_pages;
#[no_mangle]
pub unsafe extern "C" fn __wrap_malloc(size: usize) -> *mut c_void {
locked(|| after(unsafe { __real_malloc(size) }, size))
}
#[no_mangle]
pub unsafe extern "C" fn __wrap_free(ptr: *mut c_void) {
if !ptr.is_null() {
locked(|| unsafe { __real_free(ptr) });
}
}
#[no_mangle]
pub unsafe extern "C" fn __wrap_calloc(count: usize, size: usize) -> *mut c_void {
locked(|| {
after(
unsafe { __real_calloc(count, size) },
count.saturating_mul(size),
)
})
}
#[no_mangle]
pub unsafe extern "C" fn __wrap_realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
locked(|| after(unsafe { __real_realloc(ptr, size) }, size))
}
#[no_mangle]
pub unsafe extern "C" fn __wrap_posix_memalign(
out: *mut *mut c_void,
align: usize,
size: usize,
) -> i32 {
locked(|| {
let rc = unsafe { __real_posix_memalign(out, align, size) };
if rc == 0 {
after(unsafe { *out }, size);
}
rc
})
}
#[no_mangle]
pub unsafe extern "C" fn __wrap_aligned_alloc(align: usize, size: usize) -> *mut c_void {
locked(|| after(unsafe { __real_aligned_alloc(align, size) }, size))
}
#[no_mangle]
pub unsafe extern "C" fn __wrap_malloc_usable_size(ptr: *mut c_void) -> usize {
locked(|| unsafe { __real_malloc_usable_size(ptr) })
}
#[no_mangle]
pub unsafe extern "C" fn __wrap___libc_malloc(size: usize) -> *mut c_void {
locked(|| after(unsafe { __real___libc_malloc(size) }, size))
}
#[no_mangle]
pub unsafe extern "C" fn __wrap___libc_free(ptr: *mut c_void) {
if !ptr.is_null() {
locked(|| unsafe { __real___libc_free(ptr) });
}
}
#[no_mangle]
pub unsafe extern "C" fn __wrap___libc_calloc(count: usize, size: usize) -> *mut c_void {
locked(|| {
after(
unsafe { __real___libc_calloc(count, size) },
count.saturating_mul(size),
)
})
}