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) {
#[allow(deprecated)]
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 physical_memory() -> Option<u64> {
static PHYS: OnceLock<Option<u64>> = OnceLock::new();
*PHYS.get_or_init(read_physical_memory)
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn read_physical_memory() -> Option<u64> {
let pages = unsafe { libc::sysconf(libc::_SC_PHYS_PAGES) };
let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
if pages > 0 && page > 0 {
Some((pages as u64).saturating_mul(page as u64))
} else {
None
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn read_physical_memory() -> Option<u64> {
None
}
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 mut last = String::new();
for attempt in 0..8 {
let before = gmp_live_bytes();
let big = {
let mut z = rug::Integer::from(1);
z <<= 8_000_000; z
};
let during = gmp_live_bytes();
if during <= before + 500_000 {
last = format!("attempt {attempt}: shift not counted: {before} -> {during}");
drop(big);
continue;
}
drop(big);
let after = gmp_live_bytes();
if after >= during {
last = format!("attempt {attempt}: free did not decrement: {during} -> {after}");
continue;
}
return; }
panic!("GMP accounting never produced a clean sample; last failure: {last}");
}
#[test]
fn gmp_accounting_sees_flint_bignums() {
use crate::flint::FlintInteger;
assert!(install(), "GMP accounting must install");
let mut last = String::new();
for attempt in 0..8 {
let before = gmp_live_bytes();
let big = FlintInteger::from_i64(3).pow(5_000_000);
let during = gmp_live_bytes();
if during <= before + 500_000 {
last = format!("attempt {attempt}: FLINT bignum not counted: {before} -> {during}");
drop(big);
continue;
}
drop(big);
let after = gmp_live_bytes();
if after >= during {
last =
format!("attempt {attempt}: FLINT free did not decrement: {during} -> {after}");
continue;
}
return;
}
panic!("FLINT's GMP allocations are not reaching the hooks; last failure: {last}");
}
#[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);
}
}
}