use core::ffi::{c_int, c_void};
#[cfg(all(target_os = "linux", not(miri)))]
use mnemosyne_core::constants::SEGMENT_SIZE;
const PROT_NONE: c_int = 0;
const PROT_READ: c_int = 1;
const PROT_WRITE: c_int = 2;
const MAP_PRIVATE: c_int = 2;
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
const MAP_ANON: c_int = 0x1000;
#[cfg(not(any(
target_os = "macos",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
)))]
const MAP_ANON: c_int = 0x20;
const MAP_FAILED: *mut c_void = -1isize as *mut c_void;
#[cfg(all(target_os = "linux", not(miri)))]
const MADV_DONTNEED: c_int = 4;
#[cfg(all(target_os = "linux", not(miri)))]
const MADV_FREE: c_int = 8;
#[cfg(all(target_os = "linux", not(miri)))]
static MADV_FREE_SUPPORTED: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(2);
#[cfg(all(any(target_os = "macos", target_os = "freebsd"), not(miri)))]
const MADV_FREE: c_int = 5;
#[cfg(all(target_os = "linux", not(miri)))]
const MADV_HUGEPAGE: c_int = 14;
unsafe extern "C" {
fn mmap(
addr: *mut c_void,
length: usize,
prot: c_int,
flags: c_int,
fd: c_int,
offset: isize,
) -> *mut c_void;
fn munmap(addr: *mut c_void, length: usize) -> c_int;
#[cfg(all(
any(target_os = "linux", target_os = "macos", target_os = "freebsd"),
not(miri)
))]
fn madvise(addr: *mut c_void, length: usize, advice: c_int) -> c_int;
fn mprotect(addr: *mut c_void, length: usize, prot: c_int) -> c_int;
}
#[inline]
unsafe fn hint_hugepage(ptr: *mut u8, length: usize) {
#[cfg(all(target_os = "linux", not(miri)))]
{
if length >= SEGMENT_SIZE
&& mnemosyne_core::options::ENABLE_HUGEPAGE_HINT
.load(core::sync::atomic::Ordering::Relaxed)
{
let _ = unsafe { madvise(ptr as *mut c_void, length, MADV_HUGEPAGE) };
crate::recorders::record_hugepage_hint();
}
}
#[cfg(not(all(target_os = "linux", not(miri))))]
{
let _ = ptr;
let _ = length;
}
}
pub struct UnixBackend;
impl mnemosyne_core::MemoryBackend for UnixBackend {
const SUPPORTS_PAGE_RESET: bool = cfg!(any(
target_os = "linux",
target_os = "macos",
target_os = "freebsd"
));
const SUPPORTS_MAKE_GUARD: bool = true;
const SUPPORTS_DECOMMIT: bool = cfg!(any(
target_os = "linux",
target_os = "macos",
target_os = "freebsd"
));
unsafe fn allocate(size: usize) -> *mut u8 {
let ptr = unsafe {
mmap(
core::ptr::null_mut(),
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0,
)
};
if ptr == MAP_FAILED {
return core::ptr::null_mut();
}
let ptr = ptr as *mut u8;
unsafe { hint_hugepage(ptr, size) };
ptr
}
unsafe fn deallocate(ptr: *mut u8, size: usize) -> bool {
if ptr.is_null() {
return false;
}
let res = unsafe { munmap(ptr as *mut c_void, size) };
debug_assert_eq!(res, 0, "munmap failed");
res == 0
}
unsafe fn page_reset(ptr: *mut u8, size: usize) -> bool {
if ptr.is_null() || size == 0 {
return false;
}
#[cfg(all(target_os = "linux", not(miri)))]
{
let is_full_segment =
size >= SEGMENT_SIZE && (ptr as usize).is_multiple_of(SEGMENT_SIZE);
let advice = if is_full_segment {
MADV_DONTNEED
} else {
use core::sync::atomic::Ordering;
let supported = MADV_FREE_SUPPORTED.load(Ordering::Relaxed);
if supported != 0 {
MADV_FREE
} else {
MADV_DONTNEED
}
};
let res = unsafe { madvise(ptr as *mut c_void, size, advice) };
if res == 0 && advice == MADV_FREE {
crate::recorders::record_purge_only(size);
}
if res != 0 && advice == MADV_FREE {
use core::sync::atomic::Ordering;
MADV_FREE_SUPPORTED.store(0, Ordering::Relaxed);
let res2 = unsafe { madvise(ptr as *mut c_void, size, MADV_DONTNEED) };
return res2 == 0;
}
res == 0
}
#[cfg(all(any(target_os = "macos", target_os = "freebsd"), not(miri)))]
{
let res = unsafe { madvise(ptr as *mut c_void, size, MADV_FREE) };
res == 0
}
#[cfg(any(
miri,
not(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))
))]
{
let _ = ptr;
let _ = size;
false
}
}
unsafe fn make_guard(ptr: *mut u8, size: usize) -> bool {
if ptr.is_null() || size == 0 {
return false;
}
let res = unsafe { mprotect(ptr as *mut c_void, size, PROT_NONE) };
res == 0
}
unsafe fn decommit(ptr: *mut u8, size: usize) -> bool {
if ptr.is_null() || size == 0 {
return false;
}
#[cfg(all(target_os = "linux", not(miri)))]
{
use core::sync::atomic::Ordering;
let cached = MADV_FREE_SUPPORTED.load(Ordering::Relaxed);
let use_free = if cached == 2 {
let r = unsafe { madvise(ptr as *mut c_void, size, MADV_FREE) };
if r == 0 {
MADV_FREE_SUPPORTED.store(1, Ordering::Relaxed);
return true;
}
MADV_FREE_SUPPORTED.store(0, Ordering::Relaxed);
false
} else {
cached == 1
};
let advice = if use_free { MADV_FREE } else { MADV_DONTNEED };
let res = unsafe { madvise(ptr as *mut c_void, size, advice) };
if res == 0 && use_free {
crate::recorders::record_purge_only(size);
}
res == 0
}
#[cfg(all(any(target_os = "macos", target_os = "freebsd"), not(miri)))]
{
let res = unsafe { madvise(ptr as *mut c_void, size, MADV_FREE) };
res == 0
}
#[cfg(any(
miri,
not(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))
))]
{
let _ = ptr;
let _ = size;
false
}
}
}
#[cfg(all(test, target_os = "linux"))]
#[path = "unix_tests.rs"]
mod tests;