use core::ffi::c_void;
use core::sync::atomic::{AtomicBool, AtomicI64, AtomicPtr, AtomicU64, Ordering};
pub const OPTION_COUNT: usize = 38;
pub const OPTION_NAMES: [&str; OPTION_COUNT] = [
"show_errors",
"show_stats",
"verbose",
"eager_commit",
"arena_eager_commit",
"purge_decommits",
"allow_large_os_pages",
"reserve_huge_os_pages",
"reserve_huge_os_pages_at",
"reserve_os_memory",
"deprecated_segment_cache",
"deprecated_page_reset",
"abandoned_page_purge",
"deprecated_segment_reset",
"eager_commit_delay",
"purge_delay",
"use_numa_nodes",
"disallow_os_alloc",
"os_tag",
"max_errors",
"max_warnings",
"max_segment_reclaim",
"destroy_on_exit",
"arena_reserve",
"arena_purge_mult",
"purge_extend_delay",
"abandoned_reclaim_on_free",
"disallow_arena_alloc",
"retry_on_oom",
"visit_abandoned",
"guarded_min",
"guarded_max",
"guarded_precise",
"guarded_sample_rate",
"guarded_sample_seed",
"target_segments_per_thread",
"generic_collect",
"allow_thp",
];
const DEFAULTS: [i64; OPTION_COUNT] = [
0, 0, 0, 1, 2, 1, 0, 0, -1, 0, 0, 0, 0, 1, 1, -1, 0, 0, 100, 32, 32, 10, 0, 1048576, 10, 1, 1, 0, 400, 0, 0, 0, 0, 1000, 0, 0, 10000, 1, ];
static VALUES: [AtomicI64; OPTION_COUNT] = [const { AtomicI64::new(i64::MIN) }; OPTION_COUNT];
static ENV_PARSED: AtomicBool = AtomicBool::new(false);
fn ensure_init() {
if ENV_PARSED.swap(true, Ordering::AcqRel) {
return;
}
for i in 0..OPTION_COUNT {
let name = OPTION_NAMES[i].to_uppercase();
let val = std::env::var(format!("RUSTY_ALLOC_{name}"))
.or_else(|_| std::env::var(format!("MIMALLOC_{name}")))
.ok()
.and_then(|s| parse_value(&s));
let v = val.unwrap_or(DEFAULTS[i]);
VALUES[i].store(v, Ordering::Release);
}
}
fn parse_value(s: &str) -> Option<i64> {
match s.trim().to_ascii_lowercase().as_str() {
"" | "1" | "true" | "yes" | "on" => Some(1),
"0" | "false" | "no" | "off" => Some(0),
t => t.parse::<i64>().ok(),
}
}
pub fn get(option: usize) -> i64 {
if option >= OPTION_COUNT {
return 0;
}
ensure_init();
let v = VALUES[option].load(Ordering::Acquire);
if v == i64::MIN { DEFAULTS[option] } else { v }
}
pub fn set(option: usize, value: i64) {
if option < OPTION_COUNT {
ensure_init();
VALUES[option].store(value, Ordering::Release);
}
}
pub fn set_default(option: usize, value: i64) {
if option < OPTION_COUNT {
ensure_init();
let _ = VALUES[option].compare_exchange(
DEFAULTS[option],
value,
Ordering::AcqRel,
Ordering::Acquire,
);
}
}
pub fn is_enabled(option: usize) -> bool {
get(option) != 0
}
pub fn get_clamp(option: usize, min: i64, max: i64) -> i64 {
get(option).clamp(min, max)
}
pub fn get_size(option: usize) -> usize {
let v = get(option).max(0) as usize;
match option {
9 | 23 => v * 1024, _ => v,
}
}
pub fn print() {
ensure_init();
for (i, name) in OPTION_NAMES.iter().enumerate() {
out_fmt(&format!("option '{name}': {}\n", get(i)));
}
}
pub type OutputFun = unsafe extern "C" fn(msg: *const core::ffi::c_char, arg: *mut c_void);
pub type ErrorFun = unsafe extern "C" fn(err: i32, arg: *mut c_void);
pub type DeferredFreeFun = unsafe extern "C" fn(force: bool, heartbeat: u64, arg: *mut c_void);
static OUTPUT_FUN: AtomicUsize2 = AtomicUsize2::new();
static ERROR_FUN: AtomicUsize2 = AtomicUsize2::new();
static DEFERRED_FUN: AtomicUsize2 = AtomicUsize2::new();
static HEARTBEAT: AtomicU64 = AtomicU64::new(0);
struct AtomicUsize2 {
f: AtomicPtr<c_void>,
a: AtomicPtr<c_void>,
}
impl AtomicUsize2 {
const fn new() -> Self {
AtomicUsize2 {
f: AtomicPtr::new(core::ptr::null_mut()),
a: AtomicPtr::new(core::ptr::null_mut()),
}
}
fn set(&self, f: *mut c_void, a: *mut c_void) {
self.a.store(a, Ordering::Release);
self.f.store(f, Ordering::Release);
}
fn load(&self) -> (*mut c_void, *mut c_void) {
(
self.f.load(Ordering::Acquire),
self.a.load(Ordering::Acquire),
)
}
}
pub fn register_output(f: Option<OutputFun>, arg: *mut c_void) {
OUTPUT_FUN.set(f.map_or(core::ptr::null_mut(), |f| f as *mut c_void), arg);
}
pub fn register_error(f: Option<ErrorFun>, arg: *mut c_void) {
ERROR_FUN.set(f.map_or(core::ptr::null_mut(), |f| f as *mut c_void), arg);
}
pub fn register_deferred_free(f: Option<DeferredFreeFun>, arg: *mut c_void) {
DEFERRED_FUN.set(f.map_or(core::ptr::null_mut(), |f| f as *mut c_void), arg);
}
pub fn out_fmt(msg: &str) {
let (f, a) = OUTPUT_FUN.load();
if f.is_null() {
eprint!("{msg}");
return;
}
let bytes = msg.as_bytes();
let mut buf = [0u8; 512];
let n = bytes.len().min(511);
buf[..n].copy_from_slice(&bytes[..n]);
unsafe {
let fun: OutputFun = core::mem::transmute::<*mut c_void, OutputFun>(f);
fun(buf.as_ptr().cast(), a);
}
}
pub fn error(err: i32) {
let (f, a) = ERROR_FUN.load();
if !f.is_null() {
unsafe {
let fun: ErrorFun = core::mem::transmute::<*mut c_void, ErrorFun>(f);
fun(err, a);
}
} else if is_enabled(0) {
out_fmt(&format!("rusty_alloc: error {err}\n"));
}
}
pub fn deferred_free(force: bool) {
let (f, a) = DEFERRED_FUN.load();
if !f.is_null() {
let hb = HEARTBEAT.fetch_add(1, Ordering::Relaxed);
unsafe {
let fun: DeferredFreeFun = core::mem::transmute::<*mut c_void, DeferredFreeFun>(f);
fun(force, hb, a);
}
}
}