use std::ffi::c_void;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::{Once, OnceLock};
use gmp_mpfr_sys::gmp;
static LIVE_BYTES: AtomicU64 = AtomicU64::new(0);
static ORIG_ALLOC: AtomicUsize = AtomicUsize::new(0);
static ORIG_REALLOC: AtomicUsize = AtomicUsize::new(0);
static ORIG_FREE: AtomicUsize = AtomicUsize::new(0);
static INSTALLED: AtomicBool = AtomicBool::new(false);
static INSTALL_ONCE: Once = Once::new();
type AllocFn = extern "C" fn(usize) -> *mut c_void;
type ReallocFn = extern "C" fn(*mut c_void, usize, usize) -> *mut c_void;
type FreeFn = unsafe extern "C" fn(*mut c_void, usize);
fn add_live(n: usize) {
LIVE_BYTES.fetch_add(n as u64, Ordering::Relaxed);
}
fn sub_live(n: usize) {
let _ = LIVE_BYTES.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| {
Some(v.saturating_sub(n as u64))
});
}
extern "C" fn wrap_alloc(size: usize) -> *mut c_void {
let orig: AllocFn = unsafe { std::mem::transmute(ORIG_ALLOC.load(Ordering::Acquire)) };
let p = orig(size);
add_live(size);
p
}
extern "C" fn wrap_realloc(ptr: *mut c_void, old: usize, new: usize) -> *mut c_void {
let orig: ReallocFn = unsafe { std::mem::transmute(ORIG_REALLOC.load(Ordering::Acquire)) };
let p = orig(ptr, old, new);
sub_live(old);
add_live(new);
p
}
unsafe extern "C" fn wrap_free(ptr: *mut c_void, size: usize) {
let orig: FreeFn = std::mem::transmute(ORIG_FREE.load(Ordering::Acquire));
orig(ptr, size);
sub_live(size);
}
pub fn install() -> bool {
INSTALL_ONCE.call_once(|| {
let mut alloc: gmp::allocate_function = None;
let mut realloc: gmp::reallocate_function = None;
let mut free: gmp::free_function = None;
unsafe { gmp::get_memory_functions(&mut alloc, &mut realloc, &mut free) };
let (Some(alloc), Some(realloc), Some(free)) = (alloc, realloc, free) else {
return;
};
ORIG_ALLOC.store(alloc as usize, Ordering::Release);
ORIG_REALLOC.store(realloc as usize, Ordering::Release);
ORIG_FREE.store(free as usize, Ordering::Release);
unsafe {
gmp::set_memory_functions(Some(wrap_alloc), Some(wrap_realloc), Some(wrap_free));
}
INSTALLED.store(true, Ordering::Release);
});
INSTALLED.load(Ordering::Acquire)
}
pub fn is_installed() -> bool {
INSTALLED.load(Ordering::Acquire)
}
pub fn gmp_live_bytes() -> u64 {
LIVE_BYTES.load(Ordering::Relaxed)
}
pub fn address_space_limit() -> Option<u64> {
static LIMIT: OnceLock<Option<u64>> = OnceLock::new();
*LIMIT.get_or_init(read_address_space_limit)
}
#[cfg(unix)]
fn read_address_space_limit() -> Option<u64> {
let mut rl = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
if unsafe { libc::getrlimit(libc::RLIMIT_AS, &mut rl) } != 0 {
return None;
}
if rl.rlim_cur == libc::RLIM_INFINITY {
None
} else {
#[allow(clippy::unnecessary_cast)]
Some(rl.rlim_cur as u64)
}
}
#[cfg(not(unix))]
fn read_address_space_limit() -> Option<u64> {
None
}
#[cfg(target_os = "linux")]
pub fn address_space_used() -> Option<u64> {
use std::io::Read;
let mut buf = [0u8; 64];
let mut f = std::fs::File::open("/proc/self/statm").ok()?;
let n = f.read(&mut buf).ok()?;
let text = std::str::from_utf8(buf.get(..n)?).ok()?;
let pages: u64 = text.split_whitespace().next()?.parse().ok()?;
Some(pages.saturating_mul(page_size()))
}
#[cfg(not(target_os = "linux"))]
pub fn address_space_used() -> Option<u64> {
None
}
#[cfg(target_os = "linux")]
fn page_size() -> u64 {
static PAGE: OnceLock<u64> = OnceLock::new();
*PAGE.get_or_init(|| {
let n = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
if n > 0 {
n as u64
} else {
4096
}
})
}
pub fn reserve_bytes(limit: u64) -> u64 {
const FLOOR: u64 = 32 * 1024 * 1024;
const CEILING: u64 = 256 * 1024 * 1024;
(limit / 64).clamp(FLOOR, CEILING)
}
pub fn headroom_exhausted() -> Option<(u64, u64)> {
let limit = address_space_limit()?;
let used = address_space_used()?;
if used.saturating_add(reserve_bytes(limit)) >= limit {
Some((used, limit))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gmp_accounting_tracks_a_big_rational() {
assert!(install(), "GMP accounting must install");
let before = gmp_live_bytes();
let big = {
let mut z = rug::Integer::from(1);
z <<= 8_000_000; z
};
let during = gmp_live_bytes();
assert!(
during > before + 500_000,
"expected the shift to be counted: {before} -> {during}"
);
drop(big);
let after = gmp_live_bytes();
assert!(
after < during,
"freeing must decrement the live total: {during} -> {after}"
);
}
#[test]
fn reserve_is_clamped_to_the_floor_and_ceiling() {
assert_eq!(reserve_bytes(1024), 32 * 1024 * 1024);
assert_eq!(reserve_bytes(900_000_000), 32 * 1024 * 1024);
assert_eq!(reserve_bytes(64 * 1024 * 1024 * 1024), 256 * 1024 * 1024);
}
#[test]
fn headroom_guard_is_inert_without_a_limit() {
if address_space_limit().is_none() {
assert_eq!(headroom_exhausted(), None);
}
}
}