#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(any(unix, windows)))]
compile_error!("unsupported platform");
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(unix)]
use self::unix as sys;
#[cfg(windows)]
use self::windows as sys;
use core::{
fmt,
ptr::{self, NonNull},
sync::atomic::{AtomicUsize, Ordering},
};
pub mod concurrent;
pub mod vec;
#[derive(Debug)]
pub struct Allocation {
ptr: NonNull<u8>,
size: usize,
}
unsafe impl Send for Allocation {}
unsafe impl Sync for Allocation {}
impl Allocation {
#[track_caller]
pub fn new(size: usize) -> Result<Self> {
let page_size = page_size();
assert!(is_aligned(size, page_size));
let ptr = if size == 0 {
ptr::without_provenance_mut(page_size)
} else {
sys::reserve(size)?
};
Ok(Allocation {
ptr: NonNull::new(ptr.cast()).unwrap(),
size,
})
}
#[inline]
#[must_use]
pub const fn dangling(alignment: usize) -> Allocation {
assert!(alignment.is_power_of_two());
Allocation {
ptr: unsafe { NonNull::new_unchecked(ptr::without_provenance_mut(alignment)) },
size: 0,
}
}
#[inline]
#[must_use]
pub const fn ptr(&self) -> *mut u8 {
self.ptr.as_ptr()
}
#[inline]
#[must_use]
pub const fn size(&self) -> usize {
self.size
}
#[track_caller]
pub fn commit(&self, ptr: *mut u8, size: usize) -> Result {
self.check_range(ptr, size);
unsafe { sys::commit(ptr.cast(), size) }
}
#[track_caller]
pub fn decommit(&self, ptr: *mut u8, size: usize) -> Result {
self.check_range(ptr, size);
unsafe { sys::decommit(ptr.cast(), size) }
}
#[track_caller]
pub fn prefault(&self, ptr: *mut u8, size: usize) -> Result {
self.check_range(ptr, size);
sys::prefault(ptr.cast(), size)
}
#[inline(never)]
#[track_caller]
fn check_range(&self, ptr: *mut u8, size: usize) {
assert_ne!(self.size(), 0, "the allocation is dangling");
assert_ne!(size, 0);
let allocated_range = self.ptr().addr()..self.ptr().addr() + self.size();
let requested_range = ptr.addr()..ptr.addr().checked_add(size).unwrap();
assert!(allocated_range.start <= requested_range.start);
assert!(requested_range.end <= allocated_range.end);
let page_size = page_size();
assert!(is_aligned(ptr.addr(), page_size));
assert!(is_aligned(size, page_size));
}
}
impl Drop for Allocation {
fn drop(&mut self) {
if self.size != 0 {
unsafe { sys::unreserve(self.ptr.as_ptr().cast(), self.size) };
}
}
}
#[inline]
#[must_use]
pub fn page_size() -> usize {
static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
#[cold]
#[inline(never)]
fn page_size_slow() -> usize {
let page_size = sys::page_size();
assert!(page_size.is_power_of_two());
PAGE_SIZE.store(page_size, Ordering::Relaxed);
page_size
}
let cached = PAGE_SIZE.load(Ordering::Relaxed);
if cached != 0 {
cached
} else {
page_size_slow()
}
}
#[inline(always)]
#[must_use]
pub const fn align_up(val: usize, alignment: usize) -> usize {
debug_assert!(alignment.is_power_of_two());
val.wrapping_add(alignment - 1) & !(alignment - 1)
}
#[inline(always)]
#[must_use]
pub const fn align_down(val: usize, alignment: usize) -> usize {
debug_assert!(alignment.is_power_of_two());
val & !(alignment - 1)
}
const fn is_aligned(val: usize, alignment: usize) -> bool {
debug_assert!(alignment.is_power_of_two());
val & (alignment - 1) == 0
}
trait SizedTypeProperties: Sized {
const IS_ZST: bool = size_of::<Self>() == 0;
}
impl<T> SizedTypeProperties for T {}
macro_rules! assert_unsafe_precondition {
($condition:expr, $message:expr $(,)?) => {
if cfg!(debug_assertions) {
if !$condition {
crate::panic_nounwind(concat!("unsafe precondition(s) violated: ", $message));
}
}
};
}
use assert_unsafe_precondition;
#[cold]
#[inline(never)]
const fn panic_nounwind(message: &'static str) -> ! {
#[allow(improper_ctypes_definitions)]
#[inline]
const extern "C" fn inner(message: &'static str) -> ! {
panic!("{}", message);
}
inner(message)
}
pub type Result<T = (), E = Error> = ::core::result::Result<T, E>;
#[derive(Debug)]
pub struct Error {
code: i32,
}
impl Error {
#[inline]
#[must_use]
pub fn as_raw_os_error(&self) -> i32 {
self.code
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
sys::format_error(self.code, f)
}
}
impl core::error::Error for Error {}
#[cfg(unix)]
mod unix {
#![allow(non_camel_case_types)]
use super::Result;
use core::{
ffi::{CStr, c_char, c_int, c_void},
fmt, ptr, str,
};
pub fn reserve(size: usize) -> Result<*mut c_void> {
let prot = if cfg!(miri) {
libc::PROT_READ | libc::PROT_WRITE
} else {
libc::PROT_NONE
};
let flags = libc::MAP_PRIVATE | libc::MAP_ANONYMOUS;
let ptr = unsafe { libc::mmap(ptr::null_mut(), size, prot, flags, -1, 0) };
result(ptr != libc::MAP_FAILED)?;
Ok(ptr)
}
pub unsafe fn commit(ptr: *mut c_void, size: usize) -> Result {
if cfg!(miri) {
Ok(())
} else {
result(unsafe { libc::mprotect(ptr, size, libc::PROT_READ | libc::PROT_WRITE) } == 0)
}
}
pub unsafe fn decommit(ptr: *mut c_void, size: usize) -> Result {
if cfg!(miri) {
Ok(())
} else {
result(unsafe { libc::madvise(ptr, size, libc::MADV_DONTNEED) } == 0)?;
result(unsafe { libc::mprotect(ptr, size, libc::PROT_NONE) } == 0)?;
Ok(())
}
}
pub fn prefault(ptr: *mut c_void, size: usize) -> Result {
if cfg!(miri) {
Ok(())
} else {
result(unsafe { libc::madvise(ptr, size, libc::MADV_WILLNEED) } == 0)
}
}
pub unsafe fn unreserve(ptr: *mut c_void, size: usize) {
unsafe { libc::munmap(ptr, size) };
}
pub fn page_size() -> usize {
usize::try_from(unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) }).unwrap()
}
fn result(condition: bool) -> Result {
if condition {
Ok(())
} else {
Err(super::Error { code: errno() })
}
}
#[cfg(not(target_os = "vxworks"))]
fn errno() -> i32 {
unsafe { *errno_location() as i32 }
}
#[cfg(target_os = "vxworks")]
fn errno() -> i32 {
unsafe { libc::errnoGet() as i32 }
}
pub fn format_error(errnum: i32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buf = [0 as c_char; 128];
let res = unsafe {
libc::strerror_r(errnum as c_int, buf.as_mut_ptr(), buf.len() as libc::size_t)
};
assert!(res >= 0, "strerror_r failure");
let buf = unsafe { CStr::from_ptr(buf.as_ptr()) }.to_bytes();
let s = str::from_utf8(buf).unwrap_or_else(|err| {
unsafe { str::from_utf8_unchecked(&buf[..err.valid_up_to()]) }
});
f.write_str(s)
}
unsafe extern "C" {
#[cfg(not(target_os = "vxworks"))]
#[cfg_attr(
any(
target_os = "linux",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "l4re",
target_os = "hurd",
target_os = "dragonfly"
),
link_name = "__errno_location"
)]
#[cfg_attr(
any(
target_os = "netbsd",
target_os = "openbsd",
target_os = "android",
target_os = "redox",
target_env = "newlib"
),
link_name = "__errno"
)]
#[cfg_attr(
any(target_os = "solaris", target_os = "illumos"),
link_name = "___errno"
)]
#[cfg_attr(target_os = "nto", link_name = "__get_errno_ptr")]
#[cfg_attr(
any(target_os = "freebsd", target_vendor = "apple"),
link_name = "__error"
)]
#[cfg_attr(target_os = "haiku", link_name = "_errnop")]
#[cfg_attr(target_os = "aix", link_name = "_Errno")]
fn errno_location() -> *mut c_int;
}
}
#[cfg(windows)]
mod windows {
#![allow(non_camel_case_types, non_snake_case, clippy::upper_case_acronyms)]
use super::Result;
use core::{ffi::c_void, fmt, mem, ptr, str};
pub fn reserve(size: usize) -> Result<*mut c_void> {
let protect = if cfg!(miri) {
PAGE_READWRITE
} else {
PAGE_NOACCESS
};
let ptr = unsafe { VirtualAlloc(ptr::null_mut(), size, MEM_RESERVE, protect) };
result(!ptr.is_null())?;
Ok(ptr)
}
pub unsafe fn commit(ptr: *mut c_void, size: usize) -> Result {
if cfg!(miri) {
Ok(())
} else {
result(!unsafe { VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) }.is_null())
}
}
pub unsafe fn decommit(ptr: *mut c_void, size: usize) -> Result {
if cfg!(miri) {
Ok(())
} else {
result(unsafe { VirtualFree(ptr, size, MEM_DECOMMIT) } != 0)
}
}
#[cfg(not(target_vendor = "win7"))]
pub fn prefault(ptr: *mut c_void, size: usize) -> Result {
if cfg!(miri) {
Ok(())
} else {
let entry = WIN32_MEMORY_RANGE_ENTRY {
VirtualAddress: ptr,
NumberOfBytes: size,
};
result(unsafe { PrefetchVirtualMemory(GetCurrentProcess(), 1, &entry, 0) } != 0)
}
}
#[cfg(target_vendor = "win7")]
pub fn prefault(_ptr: *mut c_void, _size: usize) -> Result {
Ok(())
}
pub unsafe fn unreserve(ptr: *mut c_void, _size: usize) {
unsafe { VirtualFree(ptr, 0, MEM_RELEASE) };
}
pub fn page_size() -> usize {
let mut system_info = unsafe { mem::zeroed() };
unsafe { GetSystemInfo(&mut system_info) };
usize::try_from(system_info.dwPageSize).unwrap()
}
fn result(condition: bool) -> Result {
if condition {
Ok(())
} else {
Err(super::Error { code: errno() })
}
}
fn errno() -> i32 {
unsafe { GetLastError() }.cast_signed()
}
pub fn format_error(errnum: i32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
const BUF_LEN: u32 = 2048;
let mut errnum = errnum.cast_unsigned();
let mut buf = [0u16; BUF_LEN as usize];
let mut module = ptr::null_mut();
let mut flags = 0;
if (errnum & FACILITY_NT_BIT) != 0 {
const NTDLL_DLL: &[u16] = &[
'N' as _, 'T' as _, 'D' as _, 'L' as _, 'L' as _, '.' as _, 'D' as _, 'L' as _,
'L' as _, 0,
];
module = unsafe { GetModuleHandleW(NTDLL_DLL.as_ptr()) };
if !module.is_null() {
errnum ^= FACILITY_NT_BIT;
flags = FORMAT_MESSAGE_FROM_HMODULE;
}
}
let res = unsafe {
FormatMessageW(
flags | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
module,
errnum,
0,
buf.as_mut_ptr(),
BUF_LEN,
ptr::null(),
) as usize
};
if res == 0 {
let fm_err = errno();
return write!(
f,
"OS Error {errnum} (FormatMessageW() returned error {fm_err})",
);
}
let mut output_len = 0;
let mut output = [0u8; BUF_LEN as usize];
for c in char::decode_utf16(buf[..res].iter().copied()) {
let Ok(c) = c else {
return write!(
f,
"OS Error {errnum} (FormatMessageW() returned invalid UTF-16)",
);
};
let len = c.len_utf8();
if len > output.len() - output_len {
break;
}
c.encode_utf8(&mut output[output_len..]);
output_len += len;
}
let s = unsafe { str::from_utf8_unchecked(&output[..output_len]) };
f.write_str(s)
}
windows_targets::link!("kernel32.dll" "system" fn GetSystemInfo(
lpSystemInfo: *mut SYSTEM_INFO,
));
windows_targets::link!("kernel32.dll" "system" fn VirtualAlloc(
lpAddress: *mut c_void,
dwSize: usize,
flAllocationType: u32,
flProtect: u32,
) -> *mut c_void);
windows_targets::link!("kernel32.dll" "system" fn VirtualFree(
lpAddress: *mut c_void,
dwSize: usize,
dwFreeType: u32,
) -> i32);
#[cfg(not(target_vendor = "win7"))]
windows_targets::link!("kernel32.dll" "system" fn GetCurrentProcess() -> HANDLE);
#[cfg(not(target_vendor = "win7"))]
windows_targets::link!("kernel32.dll" "system" fn PrefetchVirtualMemory(
hProcess: HANDLE,
NumberOfEntries: usize,
VirtualAddresses: *const WIN32_MEMORY_RANGE_ENTRY,
Flags: u32,
) -> i32);
windows_targets::link!("kernel32.dll" "system" fn GetLastError() -> u32);
windows_targets::link!("kernel32.dll" "system" fn FormatMessageW(
dwFlags: u32,
lpSource: *const c_void,
dwMessageId: u32,
dwLanguageId: u32,
lpBuffer: *mut u16,
nSize: u32,
arguments: *const *const i8,
) -> u32);
windows_targets::link!("kernel32.dll" "system" fn GetModuleHandleW(
lpModuleName: *const u16,
) -> HMODULE);
#[repr(C)]
struct SYSTEM_INFO {
wProcessorArchitecture: u16,
wReserved: u16,
dwPageSize: u32,
lpMinimumApplicationAddress: *mut c_void,
lpMaximumApplicationAddress: *mut c_void,
dwActiveProcessorMask: usize,
dwNumberOfProcessors: u32,
dwProcessorType: u32,
dwAllocationGranularity: u32,
wProcessorLevel: u16,
wProcessorRevision: u16,
}
const MEM_COMMIT: u32 = 1 << 12;
const MEM_RESERVE: u32 = 1 << 13;
const MEM_DECOMMIT: u32 = 1 << 14;
const MEM_RELEASE: u32 = 1 << 15;
const PAGE_NOACCESS: u32 = 1 << 0;
const PAGE_READWRITE: u32 = 1 << 2;
#[cfg(not(target_vendor = "win7"))]
type HANDLE = isize;
#[cfg(not(target_vendor = "win7"))]
#[repr(C)]
struct WIN32_MEMORY_RANGE_ENTRY {
VirtualAddress: *mut c_void,
NumberOfBytes: usize,
}
const FACILITY_NT_BIT: u32 = 1 << 28;
const FORMAT_MESSAGE_FROM_HMODULE: u32 = 1 << 11;
const FORMAT_MESSAGE_FROM_SYSTEM: u32 = 1 << 12;
const FORMAT_MESSAGE_IGNORE_INSERTS: u32 = 1 << 9;
type HMODULE = *mut c_void;
}