mod ffi;
pub mod metrics;
use ffi as mi;
use std::{
alloc::{GlobalAlloc, Layout},
ffi::{c_long, c_void},
hint::spin_loop,
mem, ptr,
sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering},
time::Duration,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
enum OptionId {
ArenaEagerCommit = 4,
PurgeDecommits = 5,
AllowLargeOsPages = 6,
PurgeDelay = 15,
ArenaReserve = 23,
ArenaPurgeMult = 24,
AllowThp = 43,
ArenaMaxObjectSize = 45,
}
impl OptionId {
fn get(self) -> c_long {
mi::mi_option_get(self as mi::mi_option_t)
}
fn set(self, value: c_long) {
unsafe { mi::mi_option_set(self as mi::mi_option_t, value) }
}
}
static PINNED_BYTES: AtomicU64 = AtomicU64::new(0);
static OPTIONS_LOCK: AtomicBool = AtomicBool::new(false);
struct OptionsGuard;
impl OptionsGuard {
fn lock() -> Self {
while OPTIONS_LOCK
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
spin_loop();
}
Self
}
}
impl Drop for OptionsGuard {
fn drop(&mut self) {
OPTIONS_LOCK.store(false, Ordering::Release);
}
}
const ARENA_SLICE_SHIFT: u32 = if cfg!(feature = "large-objects-4mb") {
19
} else if cfg!(feature = "large-objects-2mb") {
18
} else if cfg!(feature = "large-objects-1mb") {
17
} else {
13 + size_of::<usize>().trailing_zeros()
};
pub const ARENA_SLICE_SIZE: usize = 1 << ARENA_SLICE_SHIFT;
pub const ARENA_CHUNK_SIZE: usize = 64 * size_of::<usize>() * ARENA_SLICE_SIZE;
pub const LARGE_OBJECT_LIMIT: usize = size_of::<usize>() * ARENA_SLICE_SIZE;
pub const ARENA_MAX_OBJECT_SIZE: usize = (size_of::<usize>() - 1) * ARENA_CHUNK_SIZE;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Purge {
Default,
Never,
After(Duration),
}
pub const MAX_PURGE_DELAY: Duration = Duration::from_millis(i32::MAX as u64);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Reserve {
pub bytes: usize,
pub pinned: bool,
pub prefault: bool,
}
impl Reserve {
pub const fn pinned(bytes: usize) -> Self {
Self {
bytes,
pinned: true,
prefault: false,
}
}
pub const fn unpinned(bytes: usize) -> Self {
Self {
bytes,
pinned: false,
prefault: false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Config {
pub purge: Purge,
pub reserve: Option<Reserve>,
pub allow_large_os_pages: Option<bool>,
pub transparent_huge_pages: Option<bool>,
pub arena_eager_commit: Option<bool>,
pub arena_reserve: Option<usize>,
pub arena_max_object_size: Option<usize>,
}
impl Config {
pub const DEFAULT: Self = Self {
purge: Purge::Default,
reserve: None,
allow_large_os_pages: None,
transparent_huge_pages: None,
arena_eager_commit: None,
arena_reserve: None,
arena_max_object_size: None,
};
}
impl Default for Config {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReserveOutcome {
NotRequested,
Pinned,
Reserved,
Failed,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Status {
pub configured: bool,
pub reserve: ReserveOutcome,
}
impl Status {
pub fn read() -> Self {
let Some(outcome) = STATE.load(Ordering::Acquire).checked_sub(1) else {
return Self {
configured: false,
reserve: ReserveOutcome::NotRequested,
};
};
let reserve = match outcome {
x if x == ReserveOutcome::Pinned as u8 => ReserveOutcome::Pinned,
x if x == ReserveOutcome::Reserved as u8 => ReserveOutcome::Reserved,
x if x == ReserveOutcome::Failed as u8 => ReserveOutcome::Failed,
_ => ReserveOutcome::NotRequested,
};
Self {
configured: true,
reserve,
}
}
}
const MALLOC_ALIGNMENT: usize = 16;
const WORD_ALIGNMENT: usize = size_of::<usize>();
#[inline]
const fn malloc_satisfies(size: usize, align: usize) -> bool {
align <= WORD_ALIGNMENT || (align <= MALLOC_ALIGNMENT && size & (align - 1) == 0)
}
static STATE: AtomicU8 = AtomicU8::new(0);
pub struct MiMalloc {
config: Config,
}
impl MiMalloc {
pub const fn new(config: Config) -> Self {
Self { config }
}
pub const fn config(&self) -> &Config {
&self.config
}
#[inline]
fn ensure_configured(&self) {
if STATE.load(Ordering::Acquire) == 0 {
self.configure();
}
}
#[cold]
#[inline(never)]
fn configure(&self) {
let _guard = OptionsGuard::lock();
if STATE.load(Ordering::Relaxed) != 0 {
return;
}
let outcome = apply(&self.config);
STATE.store(outcome as u8 + 1, Ordering::Release);
}
}
unsafe impl GlobalAlloc for MiMalloc {
#[inline]
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
self.ensure_configured();
if malloc_satisfies(layout.size(), layout.align()) {
mi::mi_malloc(layout.size()) as *mut u8
} else {
mi::mi_malloc_aligned(layout.size(), layout.align()) as *mut u8
}
}
#[inline]
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
self.ensure_configured();
if malloc_satisfies(layout.size(), layout.align()) {
mi::mi_zalloc(layout.size()) as *mut u8
} else {
mi::mi_zalloc_aligned(layout.size(), layout.align()) as *mut u8
}
}
#[inline]
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { mi::mi_free_size_aligned(ptr as *mut c_void, layout.size(), layout.align()) }
}
#[inline]
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
unsafe {
if malloc_satisfies(new_size, layout.align()) {
mi::mi_realloc(ptr as *mut c_void, new_size) as *mut u8
} else {
mi::mi_realloc_aligned(ptr as *mut c_void, new_size, layout.align()) as *mut u8
}
}
}
}
fn apply(config: &Config) -> ReserveOutcome {
mi::mi_process_init();
match config.purge {
Purge::Default => {}
Purge::Never => OptionId::PurgeDelay.set(-1),
Purge::After(delay) => {
OptionId::PurgeDelay.set(purge_millis(delay));
OptionId::ArenaPurgeMult.set(1);
}
}
if let Some(eager) = config.arena_eager_commit {
OptionId::ArenaEagerCommit.set(c_long::from(eager));
}
if let Some(allowed) = config.allow_large_os_pages {
OptionId::AllowLargeOsPages.set(c_long::from(allowed));
}
if let Some(allowed) = config.transparent_huge_pages {
set_transparent_huge_pages(allowed);
}
if let Some(bytes) = config.arena_reserve {
OptionId::ArenaReserve.set(size_option(bytes.div_ceil(1024)));
}
let limit = config.arena_max_object_size.map_or_else(
|| OptionId::ArenaMaxObjectSize.get(),
|bytes| size_option(bytes.div_ceil(1024)),
);
OptionId::ArenaMaxObjectSize.set(limit.min(size_option(ARENA_MAX_OBJECT_SIZE / 1024)));
match config.reserve {
None => ReserveOutcome::NotRequested,
Some(reserve) => {
let Some(size) = whole_chunks(reserve.bytes) else {
return ReserveOutcome::Failed;
};
if reserve.pinned {
return manage_pinned(size, reserve.prefault);
}
let err =
unsafe { mi::mi_reserve_os_memory_ex(size, true, true, false, ptr::null_mut()) };
if err == 0 {
ReserveOutcome::Reserved
} else {
ReserveOutcome::Failed
}
}
}
}
fn whole_chunks(bytes: usize) -> Option<usize> {
let chunk = mi::mi_arena_min_size();
Some(bytes / chunk * chunk).filter(|size| *size > 0)
}
fn size_option(kib: usize) -> c_long {
kib.min(c_long::MAX as usize / 1024) as c_long
}
fn purge_millis(delay: Duration) -> c_long {
delay.min(MAX_PURGE_DELAY).as_millis() as c_long
}
fn arena_purge_delay(delay: c_long, mult: c_long) -> Option<Duration> {
if delay < 0 || mult < 0 {
return None;
}
let (delay, mult) = (delay as u64, mult as u64);
let total = delay
.checked_mul(mult)
.filter(|total| c_long::try_from(*total).is_ok())
.unwrap_or(delay);
Some(Duration::from_millis(total))
}
fn set_transparent_huge_pages(allowed: bool) {
OptionId::AllowThp.set(if allowed { 2 } else { 0 });
#[cfg(target_os = "linux")]
unsafe {
libc::prctl(
libc::PR_SET_THP_DISABLE,
libc::c_ulong::from(!allowed),
0,
0,
0,
);
}
}
fn manage_pinned(size: usize, prefault: bool) -> ReserveOutcome {
let alignment = mi::mi_arena_min_alignment();
let Some(len) = size.checked_add(alignment) else {
return ReserveOutcome::Failed;
};
let base = unsafe {
libc::mmap(
ptr::null_mut(),
len,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
-1,
0,
)
};
if base == libc::MAP_FAILED {
return ReserveOutcome::Failed;
}
let Some(start) = (base as usize).checked_next_multiple_of(alignment) else {
unsafe { libc::munmap(base, len) };
return ReserveOutcome::Failed;
};
let head = start - base as usize;
let tail = len - head - size;
unsafe {
if head > 0 {
libc::munmap(base, head);
}
if tail > 0 {
libc::munmap((start + size) as *mut c_void, tail);
}
}
#[cfg(target_os = "linux")]
if OptionId::AllowThp.get() != 0 {
unsafe { libc::madvise(start as *mut c_void, size, libc::MADV_HUGEPAGE) };
}
if prefault {
unsafe {
let page = libc::sysconf(libc::_SC_PAGESIZE).max(4096) as usize;
let mut offset = 0;
while offset < size {
ptr::write_volatile((start + offset) as *mut u8, 0);
offset += page;
}
}
}
let managed = unsafe {
mi::mi_manage_os_memory_ex(
start as *mut c_void,
size,
true,
true,
true,
-1,
false,
ptr::null_mut(),
)
};
if managed {
PINNED_BYTES.fetch_add(size as u64, Ordering::Relaxed);
ReserveOutcome::Pinned
} else {
unsafe { libc::munmap(start as *mut c_void, size) };
ReserveOutcome::Failed
}
}
pub fn is_global_allocator() -> bool {
let probe = Box::new(0u8);
mi::mi_is_in_heap_region(ptr::from_ref::<u8>(&probe).cast())
}
pub fn version() -> u32 {
mi::mi_version() as u32
}
pub fn good_size(size: usize) -> usize {
mi::mi_good_size(size)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Effective {
pub purge: Option<Duration>,
pub purge_decommits: bool,
pub arena_eager_commit: i64,
pub allow_large_os_pages: bool,
pub transparent_huge_pages: bool,
pub arena_reserve: usize,
pub arena_max_object_size: usize,
}
impl Effective {
pub fn read() -> Self {
let _guard = OptionsGuard::lock();
#[allow(clippy::unnecessary_cast)]
let arena_eager_commit = OptionId::ArenaEagerCommit.get() as i64;
Self {
purge: arena_purge_delay(OptionId::PurgeDelay.get(), OptionId::ArenaPurgeMult.get()),
purge_decommits: OptionId::PurgeDecommits.get() != 0,
arena_eager_commit,
allow_large_os_pages: OptionId::AllowLargeOsPages.get() != 0,
transparent_huge_pages: OptionId::AllowThp.get() != 0,
arena_reserve: usize::try_from(OptionId::ArenaReserve.get())
.unwrap_or(0)
.saturating_mul(1024),
arena_max_object_size: mi::mi_arena_max_object_size(),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Stats {
pub reserved_bytes: u64,
pub pinned_bytes: u64,
pub committed_bytes: u64,
pub committed_peak_bytes: u64,
pub allocated_bytes: u64,
pub allocated_peak_bytes: u64,
pub purged_bytes: u64,
pub purge_calls: u64,
pub arena_purges: u64,
pub mmap_calls: u64,
pub commit_calls: u64,
pub pages: u64,
pub threads: u64,
pub arenas: u64,
pub huge_allocations: u64,
pub huge_bytes: u64,
}
impl Stats {
pub fn read() -> Option<Self> {
let mut raw: mi::mi_stats_t = unsafe { mem::zeroed() };
raw.size = size_of::<mi::mi_stats_t>();
raw.version = mi::MI_STAT_VERSION;
if !unsafe { mi::mi_stats_get(&mut raw) } {
return None;
}
Some(Self {
reserved_bytes: unsigned(raw.reserved.current),
pinned_bytes: PINNED_BYTES.load(Ordering::Relaxed),
committed_bytes: unsigned(raw.committed.current),
committed_peak_bytes: unsigned(raw.committed.peak),
allocated_bytes: unsigned(raw.malloc_normal.current)
+ unsigned(raw.malloc_huge.current),
allocated_peak_bytes: unsigned(raw.malloc_normal.peak) + unsigned(raw.malloc_huge.peak),
purged_bytes: unsigned(raw.purged.total),
purge_calls: unsigned(raw.purge_calls.total),
arena_purges: unsigned(raw.arena_purges.total),
mmap_calls: unsigned(raw.mmap_calls.total),
commit_calls: unsigned(raw.commit_calls.total),
pages: unsigned(raw.pages.current),
threads: unsigned(raw.threads.current),
arenas: unsigned(raw.arena_count.total),
huge_allocations: unsigned(raw.malloc_huge_count.total),
huge_bytes: unsigned(raw.malloc_huge.total),
})
}
}
fn unsigned(value: i64) -> u64 {
u64::try_from(value).unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
const RESERVE_BYTES: usize = 2 * ARENA_CHUNK_SIZE;
const PURGE_DELAY: Duration = Duration::from_secs(3600);
const MAX_OBJECT: usize = ARENA_CHUNK_SIZE + 1;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc::new(Config {
purge: Purge::After(PURGE_DELAY),
reserve: Some(Reserve::pinned(RESERVE_BYTES)),
transparent_huge_pages: Some(false),
arena_max_object_size: Some(MAX_OBJECT),
..Config::DEFAULT
});
#[test]
fn config_is_applied_before_use() {
let status = Status::read();
assert!(status.configured);
assert_eq!(status.reserve, ReserveOutcome::Pinned);
assert_eq!(OptionId::PurgeDelay.get(), 3_600_000);
assert_eq!(OptionId::ArenaPurgeMult.get(), 1);
assert_eq!(OptionId::AllowThp.get(), 0);
let effective = Effective::read();
assert_eq!(effective.purge, Some(PURGE_DELAY));
assert!(effective.purge_decommits);
assert!(!effective.transparent_huge_pages);
#[cfg(target_os = "linux")]
unsafe {
assert_eq!(libc::prctl(libc::PR_GET_THP_DISABLE, 0, 0, 0, 0), 1);
}
assert!(is_global_allocator());
assert_eq!(version(), 30503);
assert_eq!(good_size(4096), 4096);
assert_eq!(good_size(4096 + 32), 5120);
}
#[test]
fn purge_delay_is_bounded_and_mirrors_mimalloc() {
assert_eq!(purge_millis(Duration::MAX), c_long::from(i32::MAX));
assert_eq!(purge_millis(Duration::from_millis(5)), 5);
assert_eq!(arena_purge_delay(1000, 4), Some(Duration::from_secs(4)));
assert_eq!(arena_purge_delay(0, 4), Some(Duration::ZERO));
assert_eq!(arena_purge_delay(-1, 4), None);
assert_eq!(arena_purge_delay(1000, -1), None);
assert_eq!(
arena_purge_delay(c_long::MAX, 2),
Some(Duration::from_millis(c_long::MAX as u64))
);
}
#[test]
fn arena_object_limit_rounds_up_and_is_bounded() {
assert_eq!(
OptionId::ArenaMaxObjectSize.get(),
(ARENA_CHUNK_SIZE / 1024 + 1) as c_long
);
assert_eq!(
Effective::read().arena_max_object_size,
ARENA_CHUNK_SIZE + ARENA_SLICE_SIZE
);
assert_eq!(size_option(usize::MAX), c_long::MAX / 1024);
}
#[test]
fn constants_match_vendored_headers() {
let header = include_str!("../../mimalloc/include/mimalloc.h");
let body = header
.split_once("typedef enum mi_option_e {")
.and_then(|(_, rest)| rest.split_once("} mi_option_t;"))
.map(|(body, _)| body)
.expect("mimalloc.h must declare the mi_option_t enum");
let mut value = -1;
let ids: HashMap<&str, i32> = body
.lines()
.map(|line| {
line.split("//")
.next()
.unwrap()
.trim()
.trim_end_matches(',')
})
.filter(|entry| !entry.is_empty() && !entry.contains('='))
.map(|name| {
value += 1;
(name, value)
})
.collect();
for (id, name) in [
(OptionId::ArenaEagerCommit, "mi_option_arena_eager_commit"),
(OptionId::PurgeDecommits, "mi_option_purge_decommits"),
(
OptionId::AllowLargeOsPages,
"mi_option_allow_large_os_pages",
),
(OptionId::PurgeDelay, "mi_option_purge_delay"),
(OptionId::ArenaReserve, "mi_option_arena_reserve"),
(OptionId::ArenaPurgeMult, "mi_option_arena_purge_mult"),
(OptionId::AllowThp, "mi_option_allow_thp"),
(
OptionId::ArenaMaxObjectSize,
"mi_option_arena_max_object_size",
),
] {
assert_eq!(
ids.get(name),
Some(&(id as i32)),
"{name} must carry its vendored mi_option_t value"
);
}
let types = include_str!("../../mimalloc/include/mimalloc/types.h");
let max_align: usize = types
.lines()
.find_map(|line| line.strip_prefix("#define MI_MAX_ALIGN_SIZE"))
.and_then(|rest| rest.split_whitespace().next())
.and_then(|value| value.parse().ok())
.expect("types.h must define MI_MAX_ALIGN_SIZE as an integer");
assert_eq!(max_align, MALLOC_ALIGNMENT);
}
#[test]
fn stats_layout_matches_vendored_header() {
let header = include_str!("../../mimalloc/include/mimalloc-stats.h");
let define = |name: &str| -> usize {
header
.lines()
.find_map(|line| line.strip_prefix(&format!("#define {name}")))
.and_then(|rest| rest.split_whitespace().next())
.map(|value| value.trim_matches(|c| c == '(' || c == ')' || c == 'U'))
.and_then(|value| value.parse().ok())
.unwrap_or_else(|| panic!("mimalloc-stats.h must define {name} as an integer"))
};
assert_eq!(define("MI_STAT_VERSION"), mi::MI_STAT_VERSION);
assert_eq!(define("MI_BIN_HUGE"), mi::MI_BIN_HUGE);
let chunk_bins = header
.split_once("typedef enum mi_chunkbin_e {")
.and_then(|(_, rest)| rest.split_once("MI_CBIN_COUNT"))
.map(|(body, _)| body.matches("MI_CBIN_").count())
.expect("mimalloc-stats.h must declare the mi_chunkbin_e enum");
assert_eq!(chunk_bins, mi::MI_CBIN_COUNT);
let fields: Vec<(&str, &str)> = header
.lines()
.skip_while(|line| !line.starts_with("#define MI_STAT_FIELDS()"))
.skip(1)
.take_while(|line| line.trim_end().ends_with('\\'))
.filter_map(|line| {
let entry = line.trim().trim_end_matches('\\').trim();
let entry = entry.split("/*").next().unwrap().trim();
let (kind, rest) = entry.split_once('(')?;
let name = rest.split_once(')')?.0.trim();
let kind = match kind.trim() {
"MI_STAT_COUNT" => "mi_stat_count_t",
"MI_STAT_COUNTER" => "mi_stat_counter_t",
other => panic!(
"MI_STAT_FIELDS entry {other} must be MI_STAT_COUNT or MI_STAT_COUNTER"
),
};
Some((kind, name))
})
.collect();
assert_eq!(fields, mi::STAT_FIELDS);
let body = |name: &str| -> &str {
header
.split_once(&format!("typedef struct {name}"))
.and_then(|(_, rest)| rest.split_once('{'))
.and_then(|(_, rest)| rest.split_once('}'))
.map(|(body, _)| body)
.unwrap_or_else(|| panic!("mimalloc-stats.h must declare {name}"))
};
let members = |body: &str| -> Vec<String> {
body.lines()
.filter_map(|line| line.trim().split_once(';'))
.map(|(declaration, _)| {
declaration.split_whitespace().collect::<Vec<_>>().join(" ")
})
.collect()
};
assert_eq!(
members(body("mi_stat_count_s")),
["int64_t total", "int64_t peak", "int64_t current"]
);
assert_eq!(members(body("mi_stat_counter_s")), ["int64_t total"]);
let stats = body("mi_stats_s");
for array in [
"mi_stat_count_t _stat_reserved[4]",
"mi_stat_counter_t _stat_counter_reserved[4]",
"mi_stat_count_t malloc_bins[MI_BIN_HUGE+1]",
"mi_stat_count_t page_bins[MI_BIN_HUGE+1]",
"mi_stat_count_t chunk_bins[MI_CBIN_COUNT]",
] {
assert!(
members(stats).iter().any(|member| member == array),
"mimalloc-stats.h must declare {array}"
);
}
let (header_fields, _) = stats
.split_once("mi_decl_align(8)")
.expect("mimalloc-stats.h must align the first statistic to 8 bytes");
assert_eq!(members(header_fields), ["size_t size", "size_t version"]);
assert_eq!(align_of::<mi::mi_stats_t>(), 8);
}
#[test]
fn fast_paths_hand_out_aligned_memory() {
for (size, align) in [
(100usize, 1usize),
(100, 8),
(100, 16),
(1, 16),
(8, 16),
(16, 16),
(24, 16),
(32, 16),
(48, 16),
(3, 8),
(100, 64),
(4096, 4096),
(100, 4096),
] {
let layout = Layout::from_size_align(size, align).expect("layout must be valid");
unsafe {
let mut live = Vec::new();
for _ in 0..16 {
let a = GLOBAL.alloc(layout);
assert!(
!a.is_null() && (a as usize).is_multiple_of(align),
"the pointer must be non-null and aligned for {layout:?}"
);
let z = GLOBAL.alloc_zeroed(layout);
assert!(
!z.is_null() && (z as usize).is_multiple_of(align),
"the pointer must be non-null and aligned for {layout:?}"
);
assert!(std::slice::from_raw_parts(z, size).iter().all(|b| *b == 0));
live.push((a, z));
}
let grown = Layout::from_size_align(10_000, align).expect("layout must be valid");
for (a, z) in live {
let r = GLOBAL.realloc(a, layout, grown.size());
assert!(
!r.is_null() && (r as usize).is_multiple_of(align),
"the pointer must be non-null and aligned for {layout:?}"
);
GLOBAL.dealloc(r, grown);
GLOBAL.dealloc(z, layout);
}
}
}
}
#[test]
fn realloc_shrink_keeps_alignment() {
let layout = Layout::from_size_align(48, 16).expect("layout must be valid");
let shrunk = Layout::from_size_align(8, 16).expect("layout must be valid");
unsafe {
let live: Vec<*mut u8> = (0..16).map(|_| GLOBAL.alloc(layout)).collect();
for p in live {
let r = GLOBAL.realloc(p, layout, shrunk.size());
assert!(
!r.is_null() && (r as usize).is_multiple_of(16),
"the pointer must be non-null and aligned for {shrunk:?}"
);
GLOBAL.dealloc(r, shrunk);
}
}
}
#[test]
fn realloc_grows_into_the_exact_size_class() {
let layout = Layout::from_size_align(4096, 16).expect("layout must be valid");
let grown = Layout::from_size_align(8192, 16).expect("layout must be valid");
let widened = Layout::from_size_align(8192 + 16, 16).expect("layout must be valid");
unsafe {
let p = GLOBAL.realloc(GLOBAL.alloc(layout), layout, grown.size());
assert!(!p.is_null() && p.addr().is_multiple_of(16));
let q = GLOBAL.realloc(p, grown, widened.size());
assert!(!q.is_null() && q.addr().is_multiple_of(16));
assert_ne!(q, p, "a block of its exact size class must move to grow");
GLOBAL.dealloc(q, widened);
}
}
#[test]
fn stats_reflect_reservation_and_huge_allocations() {
let before = Stats::read().expect("stats must be available");
assert!(before.pinned_bytes >= RESERVE_BYTES as u64);
assert!(before.arenas >= 1);
assert!(before.reserved_bytes < RESERVE_BYTES as u64);
assert!(before.committed_bytes > 0);
assert!(before.threads >= 1);
let largest = vec![1u8; LARGE_OBJECT_LIMIT];
let after = Stats::read().expect("stats must be available");
assert_eq!(after.huge_allocations, before.huge_allocations);
drop(largest);
let size = LARGE_OBJECT_LIMIT + 1;
let block = vec![1u8; size];
let after = Stats::read().expect("stats must be available");
assert!(after.huge_allocations > before.huge_allocations);
assert!(after.huge_bytes >= before.huge_bytes + size as u64);
assert!(after.allocated_bytes >= before.allocated_bytes + size as u64);
drop(block);
let released = Stats::read().expect("stats must be available");
assert!(released.allocated_bytes < after.allocated_bytes);
}
#[test]
fn reservations_are_trimmed_to_chunks() {
let chunk = mi::mi_arena_min_size();
assert_eq!(chunk, ARENA_CHUNK_SIZE);
assert_eq!(whole_chunks(chunk - 1), None);
assert_eq!(whole_chunks(chunk + chunk / 2), Some(chunk));
assert_eq!(whole_chunks(usize::MAX), Some(usize::MAX / chunk * chunk));
let before = PINNED_BYTES.load(Ordering::Relaxed);
assert_eq!(manage_pinned(chunk, true), ReserveOutcome::Pinned);
assert_eq!(PINNED_BYTES.load(Ordering::Relaxed) - before, chunk as u64);
}
#[test]
fn stats_clamp_negative_counts() {
assert_eq!(unsigned(-1), 0);
assert_eq!(unsigned(i64::MAX), i64::MAX as u64);
}
#[test]
fn smallest_size_class_pages_stay_within_mimalloc_block_counts() {
const COUNT: usize = u16::MAX as usize + 4096;
let boxes: Vec<Box<u8>> = (0..COUNT).map(|i| Box::new(i as u8)).collect();
let mut addresses: Vec<usize> = boxes.iter().map(|b| ptr::from_ref(&**b).addr()).collect();
addresses.sort_unstable();
addresses.dedup();
assert_eq!(addresses.len(), COUNT);
let mut blocks_per_page: HashMap<usize, usize> = HashMap::new();
for address in &addresses {
*blocks_per_page
.entry(address / ARENA_SLICE_SIZE)
.or_default() += 1;
}
assert!(
blocks_per_page
.values()
.all(|count| *count <= u16::MAX as usize)
);
assert!(boxes.iter().enumerate().all(|(i, b)| **b == i as u8));
}
#[test]
fn unpinned_and_default_configs_are_constructible() {
let defaulted = MiMalloc::new(Config::DEFAULT);
assert_eq!(defaulted.config(), &Config::default());
assert!(!Reserve::unpinned(1).pinned);
}
#[test]
fn configuration_is_process_wide() {
let other = MiMalloc::new(Config {
purge: Purge::Never,
..Config::DEFAULT
});
let before = Status::read();
assert!(before.configured);
let layout = Layout::new::<u64>();
unsafe {
let block = other.alloc(layout);
assert!(!block.is_null());
other.dealloc(block, layout);
}
assert_eq!(Status::read(), before);
assert_eq!(OptionId::PurgeDelay.get(), 3_600_000);
}
}