#![allow(unsafe_code)]
#![deny(missing_docs)]
use core::ptr::NonNull;
pub const PAGE: usize = 1 << 12;
#[must_use]
#[inline]
pub fn page_size() -> usize {
PAGE
}
pub struct Reservation {
base: NonNull<u8>,
len: usize,
reservation: NonNull<u8>,
reservation_len: usize,
align: usize,
}
impl Reservation {
#[must_use]
#[inline]
pub fn as_ptr(&self) -> *mut u8 {
self.base.as_ptr()
}
#[must_use]
#[inline]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
#[inline]
pub fn reservation_ptr(&self) -> *mut u8 {
self.reservation.as_ptr()
}
#[must_use]
#[inline]
pub const fn reservation_len(&self) -> usize {
self.reservation_len
}
#[must_use]
pub fn into_parts(self) -> (*mut u8, usize, usize) {
let parts = (self.reservation.as_ptr(), self.reservation_len, self.align);
core::mem::forget(self);
parts
}
#[must_use]
pub unsafe fn from_raw_parts(
base: *mut u8,
len: usize,
reservation: *mut u8,
reservation_len: usize,
align: usize,
) -> Self {
let base_nn = NonNull::new(base).expect("from_raw_parts: base must be non-null");
let res_nn =
NonNull::new(reservation).expect("from_raw_parts: reservation must be non-null");
Self {
base: base_nn,
len,
reservation: res_nn,
reservation_len,
align,
}
}
}
impl Drop for Reservation {
fn drop(&mut self) {
unsafe { release_reservation(self.reservation, self.reservation_len, self.align) };
}
}
unsafe impl Send for Reservation {}
#[must_use]
pub fn reserve_aligned(size: usize, align: usize) -> Option<Reservation> {
if size == 0 || !align.is_power_of_two() || align < PAGE || !size.is_multiple_of(PAGE) {
return None;
}
reserve_aligned_raw(size, align).map(|(base, reservation, reservation_len)| Reservation {
base,
len: size,
reservation,
reservation_len,
align,
})
}
pub unsafe fn release(reservation: *mut u8, reservation_len: usize, align: usize) {
let nn = match NonNull::new(reservation) {
Some(n) => n,
None => return,
};
unsafe { release_reservation(nn, reservation_len, align) };
}
pub unsafe fn decommit(base: *mut u8, start: usize, end: usize) {
if start >= end || !start.is_multiple_of(PAGE) || !end.is_multiple_of(PAGE) {
return;
}
unsafe { decommit_pages_impl(base, start, end) };
}
pub unsafe fn recommit(base: *mut u8, start: usize, end: usize) {
if start >= end || !start.is_multiple_of(PAGE) || !end.is_multiple_of(PAGE) {
return;
}
unsafe { recommit_pages_impl(base, start, end) };
}
#[cfg(all(windows, not(miri)))]
fn reserve_aligned_raw(size: usize, align: usize) -> Option<(NonNull<u8>, NonNull<u8>, usize)> {
let over = size.checked_add(align)?;
let region = unsafe {
let p = winapi_virtual_alloc(over);
NonNull::new(p as *mut u8)?
};
let region_ptr = region.as_ptr();
let region_addr = region_ptr as usize;
let base_addr = align_up_addr(region_addr, align);
debug_assert!(base_addr + size <= region_addr + over);
let base = unsafe {
NonNull::new_unchecked(base_addr as *mut u8)
};
let head = base_addr - region_addr;
let tail_start = base_addr + size;
let tail_len = (region_addr + over) - tail_start;
if head > 0 {
unsafe {
winapi_virtual_decommit(region_ptr, head);
}
}
if tail_len > 0 {
unsafe {
winapi_virtual_decommit(tail_start as *mut u8, tail_len);
}
}
Some((base, region, over))
}
#[cfg(all(windows, not(miri)))]
unsafe fn release_reservation(reservation: NonNull<u8>, _reservation_len: usize, _align: usize) {
winapi_virtual_release(reservation.as_ptr());
}
#[cfg(all(windows, not(miri)))]
unsafe fn decommit_pages_impl(base: *mut u8, start: usize, end: usize) {
let len = end - start;
let addr = unsafe { base.add(start) };
unsafe { winapi_virtual_decommit(addr, len) };
}
#[cfg(all(windows, not(miri)))]
unsafe fn recommit_pages_impl(base: *mut u8, start: usize, end: usize) {
let len = end - start;
let addr = unsafe { base.add(start) };
unsafe {
VirtualAlloc(
addr as *mut core::ffi::c_void,
len,
MEM_COMMIT,
PAGE_READWRITE,
);
}
}
#[cfg(all(windows, not(miri)))]
extern "system" {
fn VirtualAlloc(
lp_address: *mut core::ffi::c_void,
dw_size: usize,
fl_allocation_type: u32,
fl_protect: u32,
) -> *mut core::ffi::c_void;
fn VirtualFree(lp_address: *mut core::ffi::c_void, dw_size: usize, dw_free_type: u32) -> i32;
}
#[cfg(all(windows, not(miri)))]
const MEM_COMMIT: u32 = 0x0000_1000;
#[cfg(all(windows, not(miri)))]
const MEM_RESERVE: u32 = 0x0000_2000;
#[cfg(all(windows, not(miri)))]
const MEM_DECOMMIT: u32 = 0x0000_4000;
#[cfg(all(windows, not(miri)))]
const MEM_RELEASE: u32 = 0x0000_8000;
#[cfg(all(windows, not(miri)))]
const PAGE_READWRITE: u32 = 0x04;
#[cfg(all(windows, not(miri)))]
unsafe fn winapi_virtual_alloc(over: usize) -> *mut core::ffi::c_void {
VirtualAlloc(
core::ptr::null_mut(),
over,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE,
)
}
#[cfg(all(windows, not(miri)))]
unsafe fn winapi_virtual_decommit(addr: *mut u8, len: usize) {
VirtualFree(addr as *mut core::ffi::c_void, len, MEM_DECOMMIT);
}
#[cfg(all(windows, not(miri)))]
unsafe fn winapi_virtual_release(addr: *mut u8) {
VirtualFree(addr as *mut core::ffi::c_void, 0, MEM_RELEASE);
}
#[cfg(all(unix, not(miri)))]
fn reserve_aligned_raw(size: usize, align: usize) -> Option<(NonNull<u8>, NonNull<u8>, usize)> {
let over = size.checked_add(align)?;
let region_ptr = unsafe {
let p = libc_mmap(over);
if p.is_null() {
return None;
}
p
};
let region_addr = region_ptr as usize;
let base_addr = align_up_addr(region_addr, align);
let base = unsafe {
NonNull::new_unchecked(base_addr as *mut u8)
};
let head = base_addr - region_addr;
let tail_start = base_addr + size;
let tail_len = (region_addr + over) - tail_start;
if head > 0 {
unsafe {
libc_munmap(region_ptr as *mut u8, head);
}
}
if tail_len > 0 {
unsafe {
libc_munmap(tail_start as *mut u8, tail_len);
}
}
Some((base, base, size))
}
#[cfg(all(unix, not(miri)))]
unsafe fn release_reservation(reservation: NonNull<u8>, reservation_len: usize, _align: usize) {
libc_munmap(reservation.as_ptr(), reservation_len);
}
#[cfg(all(unix, not(miri)))]
unsafe fn decommit_pages_impl(base: *mut u8, start: usize, end: usize) {
let len = end - start;
let addr = unsafe { base.add(start) };
unsafe { libc_madvise_dontneed(addr, len) };
}
#[cfg(all(unix, not(miri)))]
unsafe fn recommit_pages_impl(_base: *mut u8, _start: usize, _end: usize) {
}
#[cfg(all(unix, not(miri)))]
const PROT_READ: i32 = 0x1;
#[cfg(all(unix, not(miri)))]
const PROT_WRITE: i32 = 0x2;
#[cfg(all(unix, not(miri)))]
const MAP_PRIVATE: i32 = 0x02;
#[cfg(all(unix, not(miri), target_os = "linux"))]
const MAP_ANON: i32 = 0x20;
#[cfg(all(
unix,
not(miri),
any(
target_os = "macos",
target_os = "ios",
target_os = "tvos",
target_os = "watchos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
)
))]
const MAP_ANON: i32 = 0x1000;
#[cfg(all(unix, not(miri)))]
const MAP_FAILED: usize = usize::MAX;
#[cfg(all(unix, not(miri)))]
const MADV_DONTNEED: i32 = 4;
#[cfg(all(unix, not(miri)))]
extern "C" {
fn mmap(
addr: *mut core::ffi::c_void,
length: usize,
prot: i32,
flags: i32,
fd: i32,
offset: i64,
) -> *mut core::ffi::c_void;
fn munmap(addr: *mut core::ffi::c_void, length: usize) -> i32;
fn madvise(addr: *mut core::ffi::c_void, length: usize, advice: i32) -> i32;
}
#[cfg(all(unix, not(miri)))]
unsafe fn libc_mmap(len: usize) -> *mut core::ffi::c_void {
let p = mmap(
core::ptr::null_mut(),
len,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0,
);
if (p as usize) == MAP_FAILED {
core::ptr::null_mut()
} else {
p
}
}
#[cfg(all(unix, not(miri)))]
unsafe fn libc_munmap(addr: *mut u8, len: usize) {
let _ = munmap(addr as *mut core::ffi::c_void, len);
}
#[cfg(all(unix, not(miri)))]
unsafe fn libc_madvise_dontneed(addr: *mut u8, len: usize) {
let _ = madvise(addr as *mut core::ffi::c_void, len, MADV_DONTNEED);
}
#[cfg(miri)]
fn reserve_aligned_raw(size: usize, align: usize) -> Option<(NonNull<u8>, NonNull<u8>, usize)> {
use std::alloc::Layout;
let layout = Layout::from_size_align(size, align).ok()?;
let ptr = unsafe {
std::alloc::alloc(layout)
};
let base = NonNull::new(ptr)?;
Some((base, base, size))
}
#[cfg(miri)]
unsafe fn release_reservation(reservation: NonNull<u8>, reservation_len: usize, align: usize) {
use std::alloc::Layout;
let layout = Layout::from_size_align(reservation_len, align).expect("release: invalid layout");
std::alloc::dealloc(reservation.as_ptr(), layout);
}
#[cfg(miri)]
unsafe fn decommit_pages_impl(_base: *mut u8, _start: usize, _end: usize) {
}
#[cfg(miri)]
unsafe fn recommit_pages_impl(_base: *mut u8, _start: usize, _end: usize) {
}
#[cfg(not(miri))]
fn align_up_addr(addr: usize, align: usize) -> usize {
debug_assert!(align.is_power_of_two());
let mask = align - 1;
(addr + mask) & !mask
}