use std::mem::MaybeUninit;
use crate::shim::alloc;
use crate::shim::cell::UnsafeCell;
pub trait Allocator {
type Options;
fn allocate<T>(items: usize, options: Self::Options) -> impl Allocation<T>;
}
pub trait Allocation<T> {
fn ptr(&self) -> *mut UnsafeCell<MaybeUninit<T>>;
}
#[cfg(test)]
pub(crate) mod test_util {
use super::*;
pub(crate) struct NeverAlloc;
impl<T> Allocation<T> for NeverAlloc {
fn ptr(&self) -> *mut UnsafeCell<MaybeUninit<T>> {
unreachable!("NeverAlloc is a stub for trait checks only")
}
}
}
pub mod hugepages {
use super::*;
#[derive(Clone, Copy)]
pub enum HugepageSize {
H64KB,
H512KB,
H1MB,
H2MB,
H8MB,
H16MB,
H32MB,
H256MB,
H512MB,
}
impl HugepageSize {
fn as_usize_bytes(&self) -> usize {
match self {
HugepageSize::H64KB => 64 * 1024,
HugepageSize::H512KB => 512 * 1024,
HugepageSize::H1MB => 1024 * 1024,
HugepageSize::H2MB => 2 * 1024 * 1024,
HugepageSize::H8MB => 8 * 1024 * 1024,
HugepageSize::H16MB => 16 * 1024 * 1024,
HugepageSize::H32MB => 32 * 1024 * 1024,
HugepageSize::H256MB => 256 * 1024 * 1024,
HugepageSize::H512MB => 512 * 1024 * 1024,
}
}
fn as_mmap_size_flag(&self) -> libc::c_int {
match self {
HugepageSize::H64KB => libc::MAP_HUGE_64KB,
HugepageSize::H512KB => libc::MAP_HUGE_512KB,
HugepageSize::H1MB => libc::MAP_HUGE_1MB,
HugepageSize::H2MB => libc::MAP_HUGE_2MB,
HugepageSize::H8MB => libc::MAP_HUGE_8MB,
HugepageSize::H16MB => libc::MAP_HUGE_16MB,
HugepageSize::H32MB => libc::MAP_HUGE_32MB,
HugepageSize::H256MB => libc::MAP_HUGE_256MB,
HugepageSize::H512MB => libc::MAP_HUGE_512MB,
}
}
}
pub struct HugepageAllocator;
#[derive(bon::Builder)]
pub struct HugepageAllocatorOptions {
mlock: bool,
hugepage_size: HugepageSize,
}
impl Allocator for HugepageAllocator {
type Options = HugepageAllocatorOptions;
#[inline]
fn allocate<T>(items: usize, options: Self::Options) -> impl Allocation<T> {
let layout =
alloc::Layout::array::<UnsafeCell<MaybeUninit<T>>>(items).unwrap_or_else(|_| {
panic!(
"Layout::array overflow: {items} x {} bytes exceeds isize::MAX",
std::mem::size_of::<UnsafeCell<MaybeUninit<T>>>()
)
});
let page_size = options.hugepage_size.as_usize_bytes();
let alloc_size = (layout.size() + page_size - 1) & !(page_size - 1);
let ptr = unsafe {
libc::mmap(
std::ptr::null_mut(),
alloc_size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_POPULATE
| libc::MAP_PRIVATE
| libc::MAP_ANONYMOUS
| libc::MAP_HUGETLB
| options.hugepage_size.as_mmap_size_flag(),
-1,
0,
)
};
assert_ne!(
ptr,
libc::MAP_FAILED,
"hugepage mmap failed: {}",
std::io::Error::last_os_error()
);
if options.mlock {
let rc = unsafe { libc::mlock(ptr.cast(), alloc_size) };
assert_ne!(
rc,
-1,
"mlock hugepage allocation failed: {}",
std::io::Error::last_os_error()
);
}
HugePageAllocation {
ptr: ptr as *mut UnsafeCell<MaybeUninit<T>>,
length: alloc_size,
}
}
}
pub(crate) struct HugePageAllocation<T> {
ptr: *mut UnsafeCell<MaybeUninit<T>>,
length: usize,
}
unsafe impl<T: Send> Send for HugePageAllocation<T> {}
impl<T> Drop for HugePageAllocation<T> {
fn drop(&mut self) {
let rc = unsafe { libc::munmap(self.ptr.cast(), self.length) };
assert_ne!(
rc,
-1,
"failed to munmap hugepage allocation: {}",
std::io::Error::last_os_error()
);
}
}
impl<T> Allocation<T> for HugePageAllocation<T> {
#[inline]
fn ptr(&self) -> *mut UnsafeCell<MaybeUninit<T>> {
self.ptr
}
}
}
pub mod global {
use super::*;
pub struct GlobalAllocator;
#[derive(bon::Builder)]
pub struct GlobalAllocatorOptions {
mlock: bool,
}
impl Allocator for GlobalAllocator {
type Options = GlobalAllocatorOptions;
#[inline]
fn allocate<T>(items: usize, options: Self::Options) -> impl Allocation<T> {
let layout =
alloc::Layout::array::<UnsafeCell<MaybeUninit<T>>>(items).unwrap_or_else(|_| {
panic!(
"Layout::array overflow: {items} x {} bytes exceeds isize::MAX",
std::mem::size_of::<UnsafeCell<MaybeUninit<T>>>()
)
});
let ptr = unsafe { alloc::alloc(layout) };
if ptr.is_null() {
alloc::handle_alloc_error(layout);
}
if options.mlock {
let rc = unsafe { libc::mlock(ptr.cast(), layout.size()) };
assert_ne!(
rc,
-1,
"mlock global allocation failed: {}",
std::io::Error::last_os_error()
);
}
GlobalAllocation {
ptr: ptr.cast(),
layout,
}
}
}
pub(crate) struct GlobalAllocation<T> {
ptr: *mut UnsafeCell<MaybeUninit<T>>,
layout: alloc::Layout,
}
unsafe impl<T: Send> Send for GlobalAllocation<T> {}
impl<T> Drop for GlobalAllocation<T> {
fn drop(&mut self) {
unsafe { alloc::dealloc(self.ptr.cast(), self.layout) };
}
}
impl<T> Allocation<T> for GlobalAllocation<T> {
#[inline]
fn ptr(&self) -> *mut UnsafeCell<MaybeUninit<T>> {
self.ptr
}
}
}
#[cfg(test)]
#[cfg(feature = "tests_loom")]
pub(crate) mod loom {
use super::*;
pub(crate) struct LoomVecAllocator;
pub(crate) struct LoomVecAllocatorOptions;
impl Allocator for LoomVecAllocator {
type Options = LoomVecAllocatorOptions;
#[inline]
fn allocate<T>(items: usize, _options: Self::Options) -> impl Allocation<T> {
type RealT<T> = UnsafeCell<MaybeUninit<T>>;
let mut buffer: Vec<RealT<T>> = Vec::with_capacity(items);
for _ in 0..items {
buffer.push(UnsafeCell::new(MaybeUninit::uninit()));
}
let ptr = buffer.as_mut_ptr();
std::mem::forget(buffer);
LoomVecAllocation {
ptr,
capacity: items,
}
}
}
pub(crate) struct LoomVecAllocation<T> {
ptr: *mut UnsafeCell<MaybeUninit<T>>,
capacity: usize,
}
unsafe impl<T: Send> Send for LoomVecAllocation<T> {}
impl<T> Drop for LoomVecAllocation<T> {
fn drop(&mut self) {
unsafe { drop(Vec::from_raw_parts(self.ptr, self.capacity, self.capacity)) };
}
}
impl<T> Allocation<T> for LoomVecAllocation<T> {
#[inline]
fn ptr(&self) -> *mut UnsafeCell<MaybeUninit<T>> {
self.ptr
}
}
}
pub enum BackingAllocation {
Global,
Hugepages(hugepages::HugepageSize),
}