use std::{
ffi::c_void,
ops::Range,
ptr::slice_from_raw_parts_mut,
sync::atomic::{AtomicPtr, Ordering},
};
use closure_ffi::{JitAlloc, JitAllocError};
use windows_sys::Win32::System::{
Memory::{
MEM_COMMIT, MEM_FREE, MEM_RELEASE, MEM_RESERVE, MEMORY_BASIC_INFORMATION,
PAGE_EXECUTE_READWRITE, VirtualAlloc, VirtualFree, VirtualQuery,
},
SystemInformation::{GetSystemInfo, SYSTEM_INFO},
};
#[derive(Debug)]
#[repr(align(64))]
pub struct CodeBuffer {
cursor: AtomicPtr<u8>,
alloc_base: *mut c_void,
end: *mut u8,
}
unsafe impl Send for CodeBuffer {}
unsafe impl Sync for CodeBuffer {}
impl CodeBuffer {
pub fn alloc_near(region: Range<*const u8>, size: usize, max_sep: usize) -> Option<Self> {
let region = region.start.addr()..region.end.addr();
let mut si = SYSTEM_INFO::default();
unsafe { GetSystemInfo(&mut si) };
let gran = si.dwAllocationGranularity as usize;
let lowest_base = region.end.saturating_sub(max_sep).max(gran).next_multiple_of(gran);
let mut minfo = MEMORY_BASIC_INFORMATION::default();
let mut query_base = lowest_base;
while unsafe {
VirtualQuery(
query_base as *const _,
&mut minfo,
size_of::<MEMORY_BASIC_INFORMATION>(),
) != 0
} {
let block_start = (minfo.BaseAddress as usize).next_multiple_of(gran);
let block_end = (minfo.BaseAddress as usize + minfo.RegionSize) & !(gran - 1);
let block_size = block_end - block_start;
if (block_size + size).saturating_sub(region.start) > max_sep {
break;
}
else if minfo.State != MEM_FREE || size > block_size {
query_base = minfo.BaseAddress as usize + minfo.RegionSize;
continue;
}
let alloc_base = unsafe {
VirtualAlloc(
block_start as *const _,
size,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE,
)
};
assert!(!alloc_base.is_null(), "VirtualAlloc failed");
return Some(Self {
alloc_base,
cursor: AtomicPtr::new(alloc_base as *mut _),
end: unsafe { (alloc_base as *mut u8).add(size) },
});
}
None
}
pub fn reserve(&self, size: usize) -> Option<*mut [u8]> {
self.cursor
.try_update(Ordering::Relaxed, Ordering::Relaxed, |c| {
let new_cursor = c.with_addr(c.addr().checked_add(size)?);
(c < self.end).then_some(new_cursor)
})
.ok()
.map(|c| slice_from_raw_parts_mut(c, size))
}
pub fn write(&self, bytes: &[u8]) -> Option<*mut [u8]> {
self.reserve(bytes.len()).inspect(|&buf| unsafe {
(buf as *mut u8).copy_from_nonoverlapping(bytes.as_ptr(), bytes.len());
})
}
}
impl Drop for CodeBuffer {
fn drop(&mut self) {
if unsafe { VirtualFree(self.alloc_base, 0, MEM_RELEASE) } == 0 {
log::error!("VirtualFree failed")
}
}
}
impl JitAlloc for CodeBuffer {
fn alloc(&self, size: usize) -> Result<(*const u8, *mut u8), JitAllocError> {
self.reserve(size).map(|p| (p as *const u8, p as *mut u8)).ok_or(JitAllocError)
}
#[allow(unused_variables)]
unsafe fn release(&self, rx_ptr: *const u8) -> Result<(), JitAllocError> {
Ok(())
}
#[allow(unused_variables)]
unsafe fn flush_instruction_cache(&self, rx_ptr: *const u8, size: usize) {}
#[allow(unused_variables)]
unsafe fn protect_jit_memory(
&self,
ptr: *const u8,
size: usize,
access: closure_ffi::jit_alloc::ProtectJitAccess,
) {
}
}