use std::{
io::Error as IoError,
ops::Range,
ptr,
slice::{from_raw_parts, from_raw_parts_mut},
sync::OnceLock,
};
use wbase::error::{Error, Result};
use super::tracker::NativeMemoryTracker;
#[cfg(windows)]
unsafe extern "system" {
fn VirtualAlloc(
lpAddress: *mut std::ffi::c_void,
dwSize: usize,
flAllocationType: u32,
flProtect: u32,
) -> *mut std::ffi::c_void;
fn VirtualFree(lpAddress: *mut std::ffi::c_void, dwSize: usize, dwFreeType: u32) -> i32;
}
#[cfg(windows)]
const MEM_COMMIT: u32 = 0x00001000;
#[cfg(windows)]
const MEM_RESERVE: u32 = 0x00002000;
#[cfg(windows)]
const MEM_RELEASE: u32 = 0x00008000;
#[cfg(windows)]
const PAGE_READWRITE: u32 = 0x04;
#[derive(Debug)]
pub struct DirectVmBlock {
pub base_ptr: *mut u8,
pub aligned_ptr: *mut u8,
pub reserved_length: usize,
}
unsafe impl Send for DirectVmBlock {}
unsafe impl Sync for DirectVmBlock {}
impl DirectVmBlock {
pub const fn empty() -> Self {
Self {
base_ptr: ptr::null_mut(),
aligned_ptr: ptr::null_mut(),
reserved_length: 0,
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.base_ptr.is_null()
}
#[inline]
pub fn avail_len(&self) -> usize {
let offset = self.aligned_ptr as usize - self.base_ptr as usize;
self.reserved_length.saturating_sub(offset)
}
fn check_range(&self, range: Range<usize>) -> Result<(*mut u8, usize)> {
if self.is_empty() || range.start > range.end || range.end > self.avail_len() {
return Err(Error::Overflow);
}
let start_ptr = unsafe { self.aligned_ptr.add(range.start) };
Ok((start_ptr, range.end - range.start))
}
#[inline]
pub fn slice(&self, range: Range<usize>) -> Result<&[u8]> {
let (start_ptr, len) = self.check_range(range)?;
if len == 0 {
return Ok(&[]);
}
Ok(unsafe { from_raw_parts(start_ptr, len) })
}
#[inline]
pub fn as_aligned_slice(&self) -> &[u8] {
if self.is_empty() || self.aligned_ptr.is_null() || self.avail_len() == 0 {
&[]
} else {
unsafe { from_raw_parts(self.aligned_ptr, self.avail_len()) }
}
}
#[inline]
pub fn as_aligned_mut_slice(&mut self) -> &mut [u8] {
if self.is_empty() || self.aligned_ptr.is_null() || self.avail_len() == 0 {
&mut []
} else {
unsafe { from_raw_parts_mut(self.aligned_ptr, self.avail_len()) }
}
}
}
impl Drop for DirectVmBlock {
fn drop(&mut self) {
DirectVirtualMemory::free(self);
}
}
pub struct DirectVirtualMemory;
#[cfg(target_os = "linux")]
const HUGE_PAGE_SIZE: usize = 2 << 20;
const FALLBACK_PAGE_SIZE: usize = 4096;
impl DirectVirtualMemory {
pub fn allocate(size: usize, alignment: usize) -> Result<DirectVmBlock> {
if size == 0 {
return Err(Error::InvalidSize(size));
}
if alignment == 0 || !alignment.is_power_of_two() {
return Err(Error::InvalidAlignment(alignment, 1));
}
#[cfg(target_os = "linux")]
let use_huge_pages = size >= HUGE_PAGE_SIZE;
#[cfg(target_os = "linux")]
let effective_alignment = if use_huge_pages && alignment < HUGE_PAGE_SIZE {
HUGE_PAGE_SIZE
} else {
alignment
};
#[cfg(not(target_os = "linux"))]
let effective_alignment = alignment;
let page_size = system_page_size();
let total = size
.checked_add(effective_alignment)
.ok_or(Error::Overflow)?;
let reserve = (total.checked_add(page_size - 1).ok_or(Error::Overflow)?) & !(page_size - 1);
#[cfg(unix)]
let base_ptr = unsafe {
let ptr = libc::mmap(
ptr::null_mut(),
reserve,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANON,
-1,
0,
);
if ptr == libc::MAP_FAILED || ptr.is_null() {
return Err(Error::DirectVmAllocFailed {
size: reserve,
source: IoError::last_os_error(),
});
}
#[cfg(target_os = "linux")]
if use_huge_pages {
let _ = libc::madvise(ptr, reserve, libc::MADV_HUGEPAGE);
}
ptr as *mut u8
};
#[cfg(windows)]
let base_ptr = unsafe {
let ptr = VirtualAlloc(
ptr::null_mut(),
reserve,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE,
);
if ptr.is_null() {
return Err(Error::DirectVmAllocFailed {
size: reserve,
source: IoError::last_os_error(),
});
}
ptr as *mut u8
};
let base_addr = base_ptr as usize;
let aligned_addr = match base_addr.checked_add(effective_alignment - 1) {
Some(addr) => addr & !(effective_alignment - 1),
None => {
#[cfg(unix)]
unsafe {
libc::munmap(base_ptr as *mut libc::c_void, reserve);
}
#[cfg(windows)]
unsafe {
VirtualFree(base_ptr as *mut std::ffi::c_void, 0, MEM_RELEASE);
}
return Err(Error::Overflow);
}
};
let aligned_ptr = aligned_addr as *mut u8;
NativeMemoryTracker::add(reserve);
Ok(DirectVmBlock {
base_ptr,
aligned_ptr,
reserved_length: reserve,
})
}
pub fn free(block: &mut DirectVmBlock) {
if block.base_ptr.is_null() || block.reserved_length == 0 {
return;
}
let freed = {
#[cfg(unix)]
unsafe {
libc::munmap(block.base_ptr as *mut libc::c_void, block.reserved_length) == 0
}
#[cfg(windows)]
unsafe {
VirtualFree(block.base_ptr as *mut std::ffi::c_void, 0, MEM_RELEASE) != 0
}
};
if freed {
NativeMemoryTracker::subtract(block.reserved_length);
} else {
log::error!(
"直接虚拟内存释放失败: ptr={:?}, len={}",
block.base_ptr,
block.reserved_length
);
}
block.base_ptr = ptr::null_mut();
block.aligned_ptr = ptr::null_mut();
block.reserved_length = 0;
}
#[inline]
pub unsafe fn clear(ptr: *mut u8, len: usize) {
if !ptr.is_null() && len > 0 {
unsafe { ptr::write_bytes(ptr, 0, len) };
}
}
}
#[inline]
pub fn system_page_size() -> usize {
static PAGE_SIZE: OnceLock<usize> = OnceLock::new();
*PAGE_SIZE.get_or_init(|| {
#[cfg(unix)]
unsafe {
let val = libc::sysconf(libc::_SC_PAGESIZE);
if val > 0 {
val as usize
} else {
FALLBACK_PAGE_SIZE
}
}
#[cfg(not(unix))]
{
FALLBACK_PAGE_SIZE
}
})
}