use near_vm_compiler::CompileError;
#[cfg(not(windows))]
use rustix::mm::{self, MapFlags, MprotectFlags, ProtFlags};
#[cfg(not(windows))]
use std::sync::Arc;
#[cfg(not(windows))]
pub(crate) const ARCH_FUNCTION_ALIGNMENT: u16 = 16;
#[cfg(not(windows))]
pub(crate) const DATA_SECTION_ALIGNMENT: u16 = 64;
fn round_up(size: usize, multiple: usize) -> usize {
debug_assert!(multiple.is_power_of_two());
(size + (multiple - 1)) & !(multiple - 1)
}
pub struct CodeMemoryWriter<'a> {
memory: &'a mut CodeMemory,
offset: usize,
}
impl<'a> CodeMemoryWriter<'a> {
#[cfg(not(windows))]
pub fn write_data(&mut self, mut alignment: u16, input: &[u8]) -> Result<usize, CompileError> {
if self.offset == self.memory.executable_end {
alignment = u16::try_from(rustix::param::page_size()).expect("page size > u16::MAX");
}
self.write_inner(alignment, input)
}
pub fn write_executable(
&mut self,
alignment: u16,
input: &[u8],
) -> Result<usize, CompileError> {
assert_eq!(
self.memory.executable_end, self.offset,
"may not interleave executable and data in the same map"
);
let result = self.write_inner(alignment, input);
self.memory.executable_end = self.offset;
result
}
fn write_inner(&mut self, alignment: u16, input: &[u8]) -> Result<usize, CompileError> {
let entry_offset = self.offset;
let aligned_offset = round_up(entry_offset, usize::from(alignment));
let final_offset = aligned_offset + input.len();
let out_buffer = self.memory.as_slice_mut();
out_buffer
.get_mut(entry_offset..aligned_offset)
.ok_or_else(|| CompileError::Resource("out of code memory space".into()))?
.fill(0);
out_buffer
.get_mut(aligned_offset..final_offset)
.ok_or_else(|| CompileError::Resource("out of code memory space".into()))?
.copy_from_slice(input);
self.offset = final_offset;
Ok(aligned_offset)
}
pub fn position(&self) -> usize {
self.offset
}
}
pub struct CodeMemory {
#[cfg(not(windows))]
source_pool: Option<Arc<parking_lot::Mutex<Vec<Self>>>>,
map: *mut u8,
size: usize,
executable_end: usize,
}
impl CodeMemory {
#[cfg(not(windows))]
fn create(size: usize) -> rustix::io::Result<Self> {
assert!(size != 0);
let size = round_up(size, rustix::param::page_size());
let map = unsafe {
mm::mmap_anonymous(
std::ptr::null_mut(),
size,
ProtFlags::WRITE | ProtFlags::READ,
MapFlags::SHARED,
)?
};
Ok(Self { source_pool: None, map: map.cast(), executable_end: 0, size })
}
fn as_slice_mut(&mut self) -> &mut [u8] {
unsafe {
std::slice::from_raw_parts_mut(self.map, self.size)
}
}
#[cfg(not(windows))]
pub fn resize(mut self, size: usize) -> rustix::io::Result<Self> {
if self.size < size {
let mut result = Self::create(size)?;
result.source_pool = self.source_pool.take();
drop(self);
Ok(result)
} else {
self.executable_end = 0;
Ok(self)
}
}
pub unsafe fn writer(&mut self) -> CodeMemoryWriter<'_> {
self.executable_end = 0;
CodeMemoryWriter { memory: self, offset: 0 }
}
#[cfg(not(windows))]
pub unsafe fn publish(&mut self) -> Result<(), CompileError> {
unsafe {
mm::mprotect(
self.map.cast(),
self.executable_end,
MprotectFlags::EXEC | MprotectFlags::READ,
)
}
.map_err(|e| {
CompileError::Resource(format!("could not make code memory executable: {}", e))
})
}
pub unsafe fn executable_address(&self, offset: usize) -> *const u8 {
debug_assert!(offset <= isize::MAX as usize);
unsafe { self.map.offset(offset as isize) }
}
pub unsafe fn writable_address(&self, offset: usize) -> *mut u8 {
debug_assert!(offset <= isize::MAX as usize);
unsafe { self.map.offset(offset as isize) }
}
}
#[cfg(not(windows))]
impl Drop for CodeMemory {
fn drop(&mut self) {
if let Some(source_pool) = self.source_pool.take() {
unsafe {
let result = mm::mprotect(
self.map.cast(),
self.size,
MprotectFlags::WRITE | MprotectFlags::READ,
);
if let Err(e) = result {
panic!(
"could not mprotect mapping before returning it to the memory pool: \
map={:?}, size={:?}, error={}",
self.map, self.size, e
);
}
}
let mut guard = source_pool.lock();
guard.push(Self {
source_pool: None,
map: self.map,
size: self.size,
executable_end: 0,
});
} else {
unsafe {
if let Err(e) = mm::munmap(self.map.cast(), self.size) {
tracing::error!(
target: "near_vm",
message="could not unmap mapping",
map=?self.map, size=self.size, error=%e
);
}
}
}
}
}
unsafe impl Send for CodeMemory {}
#[derive(Clone)]
pub struct MemoryPool {
#[cfg(not(windows))]
pool: Arc<parking_lot::Mutex<Vec<CodeMemory>>>,
}
#[cfg(not(windows))]
impl MemoryPool {
pub fn new(preallocate_count: usize, initial_map_size: usize) -> rustix::io::Result<Self> {
let mut pool = Vec::with_capacity(preallocate_count);
for _ in 0..preallocate_count {
pool.push(CodeMemory::create(initial_map_size)?);
}
let pool = Arc::new(parking_lot::Mutex::new(pool));
Ok(Self { pool })
}
pub fn get(&self, size: usize) -> rustix::io::Result<CodeMemory> {
let mut guard = self.pool.lock();
let mut memory = match guard.pop() {
Some(m) => m,
None => CodeMemory::create(std::cmp::max(size, 1))?,
};
memory.source_pool = Some(Arc::clone(&self.pool));
if memory.size < size { Ok(memory.resize(size)?) } else { Ok(memory) }
}
}
#[cfg(test)]
mod tests {
use super::CodeMemory;
fn _assert() {
fn _assert_send<T: Send>() {}
_assert_send::<CodeMemory>();
}
}