use core::ffi::c_void;
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
#[cfg(target_has_atomic = "64")]
use core::sync::atomic::{AtomicI64, AtomicU64};
#[cfg(all(not(target_has_atomic = "64"), feature = "std"))]
use portable_atomic::{AtomicI64, AtomicU64};
#[cfg(all(not(target_has_atomic = "64"), not(feature = "std")))]
use split64::{AtomicI64, AtomicU64};
#[cfg(any(all(not(target_has_atomic = "64"), not(feature = "std")), test))]
mod split64 {
use core::sync::atomic::{AtomicU32, Ordering};
const fn split(v: u64) -> (u32, u32) {
(v as u32, (v >> 32) as u32)
}
const fn join(lo: u32, hi: u32) -> u64 {
((hi as u64) << 32) | lo as u64
}
#[derive(Debug)]
pub struct AtomicU64 {
lo: AtomicU32,
hi: AtomicU32,
}
impl AtomicU64 {
pub const fn new(v: u64) -> Self {
let (lo, hi) = split(v);
Self {
lo: AtomicU32::new(lo),
hi: AtomicU32::new(hi),
}
}
pub fn load(&self, _ord: Ordering) -> u64 {
join(
self.lo.load(Ordering::Acquire),
self.hi.load(Ordering::Acquire),
)
}
pub fn store(&self, v: u64, _ord: Ordering) {
let (lo, hi) = split(v);
self.lo.store(lo, Ordering::Release);
self.hi.store(hi, Ordering::Release);
}
pub fn fetch_add(&self, v: u64, ord: Ordering) -> u64 {
let prev = self.load(ord);
self.store(prev.wrapping_add(v), ord);
prev
}
}
#[derive(Debug)]
pub struct AtomicI64(AtomicU64);
impl AtomicI64 {
pub const fn new(v: i64) -> Self {
Self(AtomicU64::new(v as u64))
}
pub fn load(&self, ord: Ordering) -> i64 {
self.0.load(ord) as i64
}
pub fn store(&self, v: i64, ord: Ordering) {
self.0.store(v as u64, ord);
}
pub fn compare_exchange(
&self,
current: i64,
new: i64,
success: Ordering,
_failure: Ordering,
) -> Result<i64, i64> {
let seen = self.load(success);
if seen == current {
self.store(new, success);
Ok(seen)
} else {
Err(seen)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_ordering_options_uses_is_accepted() {
let v = AtomicI64::new(i64::MIN);
v.store(-1, Ordering::Release);
assert_eq!(v.load(Ordering::Acquire), -1);
assert_eq!(
v.compare_exchange(-1, 7, Ordering::AcqRel, Ordering::Acquire),
Ok(-1)
);
assert_eq!(v.load(Ordering::Acquire), 7);
assert_eq!(
v.compare_exchange(-1, 9, Ordering::AcqRel, Ordering::Acquire),
Err(7)
);
let u = AtomicU64::new(u64::from(u32::MAX));
assert_eq!(u.fetch_add(1, Ordering::Relaxed), u64::from(u32::MAX));
assert_eq!(u.load(Ordering::Relaxed), 1u64 << 32);
}
}
}
pub const OPTION_COUNT: usize = 38;
pub const GENERIC_COLLECT: usize = 36;
const _: () = assert!(GENERIC_COLLECT < OPTION_COUNT);
#[cfg(not(ra_small_profile))]
pub const GENERIC_COLLECT_DEFAULT: i64 = 10_000;
#[cfg(ra_small_profile)]
pub const GENERIC_COLLECT_DEFAULT: i64 = if cfg!(ra_generic_collect = "64") {
64
} else if cfg!(ra_generic_collect = "4096") {
4096
} else if cfg!(ra_generic_collect = "65536") {
65_536
} else {
512
};
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, 1_048_576, 10, 1, 1, 0, 400, 0, 0,
0,
0, 1000, 0, 0, GENERIC_COLLECT_DEFAULT, 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 {
VALUES[i].store(DEFAULTS[i], Ordering::Release);
}
#[cfg(all(
feature = "std",
not(all(target_arch = "wasm32", target_os = "unknown"))
))]
for i in 0..OPTION_COUNT {
let name = OPTION_NAMES[i].to_uppercase();
let val = std::env::var(std::format!("RUSTY_ALLOC_{name}"))
.or_else(|_| std::env::var(std::format!("MIMALLOC_{name}")))
.ok()
.and_then(|s| parse_value(&s));
if let Some(v) = val {
VALUES[i].store(v, Ordering::Release);
}
}
}
#[cfg(feature = "std")]
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;
}
if crate::ONE_REGION {
return DEFAULTS[option];
}
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 crate::ONE_REGION {
return;
}
if option < OPTION_COUNT {
ensure_init();
VALUES[option].store(value, Ordering::Release);
}
}
pub fn set_default(option: usize, value: i64) {
if crate::ONE_REGION {
return;
}
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,
}
}
#[cfg(feature = "std")]
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 {
#[inline]
fn load_fun(&self) -> *mut c_void {
self.f.load(Ordering::Acquire)
}
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() {
#[cfg(feature = "std")]
{
use std::io::Write;
let _ = std::io::stderr().write_all(msg.as_bytes());
}
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);
}
}
fn render_error(buf: &mut [u8; 32], err: i32) -> &str {
const PREFIX: &[u8] = b"rusty_alloc: error ";
buf[..PREFIX.len()].copy_from_slice(PREFIX);
let mut n = PREFIX.len();
if err < 0 {
buf[n] = b'-';
n += 1;
}
let mut v = err.unsigned_abs();
let mut digits = [0u8; 10];
let mut d = 0;
loop {
digits[d] = b'0' + (v % 10) as u8;
d += 1;
v /= 10;
if v == 0 {
break;
}
}
while d > 0 {
d -= 1;
buf[n] = digits[d];
n += 1;
}
buf[n] = b'\n';
n += 1;
core::str::from_utf8(&buf[..n]).unwrap_or(
"rusty_alloc: error
",
)
}
pub fn deferred_free(force: bool) {
if DEFERRED_FUN.load_fun().is_null() {
return;
}
fire_deferred(force);
}
#[cold]
#[inline(never)]
fn fire_deferred(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);
}
}
}
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) {
let mut buf = [0u8; 32];
out_fmt(render_error(&mut buf, err));
}
}
#[cfg(test)]
mod render_error_tests {
use super::render_error;
#[test]
fn renders_every_shape_without_a_formatter() {
let mut b = [0u8; 32];
assert_eq!(render_error(&mut b, 0), "rusty_alloc: error 0\n");
assert_eq!(render_error(&mut b, 7), "rusty_alloc: error 7\n");
assert_eq!(render_error(&mut b, 12345), "rusty_alloc: error 12345\n");
assert_eq!(render_error(&mut b, -1), "rusty_alloc: error -1\n");
assert_eq!(
render_error(&mut b, i32::MAX),
"rusty_alloc: error 2147483647\n"
);
assert_eq!(
render_error(&mut b, i32::MIN),
"rusty_alloc: error -2147483648\n"
);
}
}