use std::alloc::{Layout, alloc, dealloc};
use std::ptr;
use crate::common::diagnostics::NamErrorCode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AllocInfo {
Heap,
MmapAnon {
size_bytes: usize,
},
HugeTlb2M {
size_bytes: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HugePageStatus {
Explicit2MB,
Transparent,
Heap,
}
pub const HUGE_PAGE_THRESHOLD: usize = 1 << 20;
const PAGE_4K: usize = 4096;
pub const HUGE_PAGE_2M: usize = 2 * 1024 * 1024;
const fn align_up(size: usize, alignment: usize) -> usize {
(size + alignment - 1) & !(alignment - 1)
}
pub fn allocate_huge_pages(
size_bytes: usize,
) -> Result<(*mut u8, AllocInfo, HugePageStatus), NamErrorCode> {
if size_bytes < HUGE_PAGE_THRESHOLD {
let layout =
Layout::from_size_align(size_bytes, 64).map_err(|_| NamErrorCode::OutOfMemory)?;
let ptr = unsafe { alloc(layout) };
if ptr.is_null() {
return Err(NamErrorCode::OutOfMemory);
}
return Ok((ptr, AllocInfo::Heap, HugePageStatus::Heap));
}
let huge_2m_size = align_up(size_bytes, HUGE_PAGE_2M);
let ptr = unsafe { try_mmap_huge(ptr::null_mut(), huge_2m_size, -1, 0, true) };
if ptr != libc::MAP_FAILED {
return Ok((
ptr as *mut u8,
AllocInfo::HugeTlb2M {
size_bytes: huge_2m_size,
},
HugePageStatus::Explicit2MB,
));
}
let thp_size = align_up(size_bytes, PAGE_4K);
let ptr = unsafe { try_mmap_huge(ptr::null_mut(), thp_size, -1, 0, false) };
if ptr != libc::MAP_FAILED {
let _madvise_rc = unsafe { libc::madvise(ptr, thp_size, libc::MADV_HUGEPAGE) };
let collapse_rc = unsafe { libc::madvise(ptr, thp_size, libc::MADV_COLLAPSE) };
let status = if collapse_rc == 0 {
HugePageStatus::Transparent
} else {
HugePageStatus::Heap
};
return Ok((
ptr as *mut u8,
AllocInfo::MmapAnon {
size_bytes: thp_size,
},
status,
));
}
let layout = Layout::from_size_align(size_bytes, 64).map_err(|_| NamErrorCode::OutOfMemory)?;
let ptr = unsafe { alloc(layout) };
if ptr.is_null() {
return Err(NamErrorCode::OutOfMemory);
}
Ok((ptr, AllocInfo::Heap, HugePageStatus::Heap))
}
pub unsafe fn deallocate_huge(ptr: *mut u8, info: AllocInfo, size_bytes: usize) {
match info {
AllocInfo::Heap => {
if let Ok(layout) = Layout::from_size_align(size_bytes, 64) {
unsafe { dealloc(ptr, layout) };
}
}
AllocInfo::MmapAnon {
size_bytes: mmap_size,
}
| AllocInfo::HugeTlb2M {
size_bytes: mmap_size,
} => {
unsafe { libc::munmap(ptr as *mut libc::c_void, mmap_size) };
}
}
}
#[cfg(target_os = "linux")]
pub(crate) unsafe fn create_backing_fd(
size: usize,
use_huge: bool,
) -> std::io::Result<std::os::raw::c_int> {
if use_huge {
const MFD_HUGETLB: libc::c_uint = 0x0004;
const MFD_NOEXEC_SEAL: libc::c_uint = 0x0008;
let fd = unsafe {
libc::memfd_create(
c"nam_huge_buf".as_ptr(),
libc::MFD_CLOEXEC | MFD_HUGETLB | MFD_NOEXEC_SEAL,
)
};
if fd != -1 {
if unsafe { libc::ftruncate(fd, size as libc::off_t) } != -1 {
return Ok(fd);
}
let err = std::io::Error::last_os_error();
unsafe { libc::close(fd) };
return Err(err);
}
}
const MFD_NOEXEC_SEAL: libc::c_uint = 0x0008;
let fd =
unsafe { libc::memfd_create(c"nam_buf".as_ptr(), libc::MFD_CLOEXEC | MFD_NOEXEC_SEAL) };
if fd == -1 {
return Err(std::io::Error::last_os_error());
}
if unsafe { libc::ftruncate(fd, size as libc::off_t) } == -1 {
let err = std::io::Error::last_os_error();
unsafe { libc::close(fd) };
return Err(err);
}
Ok(fd)
}
#[cfg(not(target_os = "linux"))]
pub(crate) unsafe fn create_backing_fd(
_size: usize,
_use_huge: bool,
) -> std::io::Result<std::os::raw::c_int> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"create_backing_fd is only supported on Linux",
))
}
pub(crate) unsafe fn try_mmap_huge(
addr: *mut libc::c_void,
len: usize,
fd: std::os::raw::c_int,
offset: libc::off_t,
use_huge: bool,
) -> *mut libc::c_void {
let mut flags = if fd == -1 {
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS
} else {
libc::MAP_SHARED
};
if use_huge {
flags |= libc::MAP_HUGETLB | libc::MAP_HUGE_2MB;
}
unsafe {
libc::mmap(
addr,
len,
libc::PROT_READ | libc::PROT_WRITE,
flags,
fd,
offset,
)
}
}
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;
#[derive(Debug)]
pub struct HugePageVec<T> {
ptr: NonNull<T>,
len: usize,
alloc_info: AllocInfo,
}
impl<T> HugePageVec<T> {
pub fn new(len: usize, default: T) -> Result<(Self, HugePageStatus), NamErrorCode>
where
T: Copy,
{
let (mut vec, status) = Self::with_capacity(len)?;
unsafe {
for i in 0..len {
vec.ptr.as_ptr().add(i).write(default);
}
}
vec.len = len;
Ok((vec, status))
}
pub fn with_capacity(capacity: usize) -> Result<(Self, HugePageStatus), NamErrorCode> {
if capacity == 0 {
return Ok((
Self {
ptr: NonNull::dangling(),
len: 0,
alloc_info: AllocInfo::Heap,
},
HugePageStatus::Heap,
));
}
let size_bytes = capacity * std::mem::size_of::<T>();
let (ptr, alloc_info, status) = allocate_huge_pages(size_bytes)?;
Ok((
Self {
ptr: NonNull::new(ptr as *mut T).unwrap(),
len: 0,
alloc_info,
},
status,
))
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn huge_page_status(&self) -> HugePageStatus {
match self.alloc_info {
AllocInfo::Heap => HugePageStatus::Heap,
AllocInfo::MmapAnon { .. } => HugePageStatus::Transparent,
AllocInfo::HugeTlb2M { .. } => HugePageStatus::Explicit2MB,
}
}
}
impl<T> Deref for HugePageVec<T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
if self.len == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
}
}
impl<T> DerefMut for HugePageVec<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
if self.len == 0 {
&mut []
} else {
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
}
}
}
impl<T> Drop for HugePageVec<T> {
fn drop(&mut self) {
if self.len > 0 {
unsafe {
deallocate_huge(
self.ptr.as_ptr() as *mut u8,
self.alloc_info,
self.len * std::mem::size_of::<T>(),
);
}
}
}
}
unsafe impl<T: Send> Send for HugePageVec<T> {}
unsafe impl<T: Sync> Sync for HugePageVec<T> {}