use crate::mmap::Mmap;
use crate::vmcontext::VMMemoryDefinition;
use more_asserts::assert_ge;
use std::borrow::BorrowMut;
use std::cell::UnsafeCell;
use std::convert::TryInto;
use std::fmt;
use std::ptr::NonNull;
use std::sync::Mutex;
use thiserror::Error;
use unc_vm_types::{Bytes, MemoryType, Pages};
#[derive(Error, Debug, Clone, PartialEq, Hash)]
pub enum MemoryError {
#[error("Error when allocating memory: {0}")]
Region(String),
#[error("The memory could not grow: current size {} pages, requested increase: {} pages", current.0, attempted_delta.0)]
CouldNotGrow {
current: Pages,
attempted_delta: Pages,
},
#[error("The memory is invalid because {}", reason)]
InvalidMemory {
reason: String,
},
#[error("The minimum requested ({} pages) memory is greater than the maximum allowed memory ({} pages)", min_requested.0, max_allowed.0)]
MinimumMemoryTooLarge {
min_requested: Pages,
max_allowed: Pages,
},
#[error("The maximum requested memory ({} pages) is greater than the maximum allowed memory ({} pages)", max_requested.0, max_allowed.0)]
MaximumMemoryTooLarge {
max_requested: Pages,
max_allowed: Pages,
},
#[error("A user-defined error occurred: {0}")]
Generic(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
pub enum MemoryStyle {
Dynamic {
offset_guard_size: u64,
},
Static {
bound: Pages,
offset_guard_size: u64,
},
}
impl MemoryStyle {
pub fn offset_guard_size(&self) -> u64 {
match self {
Self::Dynamic { offset_guard_size } => *offset_guard_size,
Self::Static { offset_guard_size, .. } => *offset_guard_size,
}
}
}
pub trait Memory: fmt::Debug + Send + Sync {
fn ty(&self) -> MemoryType;
fn style(&self) -> &MemoryStyle;
fn size(&self) -> Pages;
fn grow(&self, delta: Pages) -> Result<Pages, MemoryError>;
fn vmmemory(&self) -> NonNull<VMMemoryDefinition>;
}
#[derive(Debug)]
pub struct LinearMemory {
mmap: Mutex<WasmMmap>,
maximum: Option<Pages>,
memory: MemoryType,
style: MemoryStyle,
offset_guard_size: usize,
vm_memory_definition: VMMemoryDefinitionOwnership,
}
#[derive(Debug)]
enum VMMemoryDefinitionOwnership {
VMOwned(NonNull<VMMemoryDefinition>),
HostOwned(Box<UnsafeCell<VMMemoryDefinition>>),
}
unsafe impl Send for LinearMemory {}
unsafe impl Sync for LinearMemory {}
#[derive(Debug)]
struct WasmMmap {
alloc: Mmap,
size: Pages,
}
impl LinearMemory {
pub fn new(memory: &MemoryType, style: &MemoryStyle) -> Result<Self, MemoryError> {
unsafe { Self::new_internal(memory, style, None) }
}
pub unsafe fn from_definition(
memory: &MemoryType,
style: &MemoryStyle,
vm_memory_location: NonNull<VMMemoryDefinition>,
) -> Result<Self, MemoryError> {
Self::new_internal(memory, style, Some(vm_memory_location))
}
unsafe fn new_internal(
memory: &MemoryType,
style: &MemoryStyle,
vm_memory_location: Option<NonNull<VMMemoryDefinition>>,
) -> Result<Self, MemoryError> {
if memory.minimum > Pages::max_value() {
return Err(MemoryError::MinimumMemoryTooLarge {
min_requested: memory.minimum,
max_allowed: Pages::max_value(),
});
}
if let Some(max) = memory.maximum {
if max > Pages::max_value() {
return Err(MemoryError::MaximumMemoryTooLarge {
max_requested: max,
max_allowed: Pages::max_value(),
});
}
if max < memory.minimum {
return Err(MemoryError::InvalidMemory {
reason: format!(
"the maximum ({} pages) is less than the minimum ({} pages)",
max.0, memory.minimum.0
),
});
}
}
let offset_guard_bytes = style.offset_guard_size() as usize;
let minimum_pages = match style {
MemoryStyle::Dynamic { .. } => memory.minimum,
MemoryStyle::Static { bound, .. } => {
assert_ge!(*bound, memory.minimum);
*bound
}
};
let minimum_bytes = minimum_pages.bytes().0;
let request_bytes = minimum_bytes.checked_add(offset_guard_bytes).unwrap();
let mapped_pages = memory.minimum;
let mapped_bytes = mapped_pages.bytes();
let mut mmap = WasmMmap {
alloc: Mmap::accessible_reserved(mapped_bytes.0, request_bytes)
.map_err(MemoryError::Region)?,
size: memory.minimum,
};
let base_ptr = mmap.alloc.as_mut_ptr();
let mem_length = memory.minimum.bytes().0;
Ok(Self {
mmap: Mutex::new(mmap),
maximum: memory.maximum,
offset_guard_size: offset_guard_bytes,
vm_memory_definition: if let Some(mem_loc) = vm_memory_location {
{
let mut ptr = mem_loc;
let md = ptr.as_mut();
md.base = base_ptr;
md.current_length = mem_length;
}
VMMemoryDefinitionOwnership::VMOwned(mem_loc)
} else {
VMMemoryDefinitionOwnership::HostOwned(Box::new(UnsafeCell::new(
VMMemoryDefinition { base: base_ptr, current_length: mem_length },
)))
},
memory: *memory,
style: style.clone(),
})
}
unsafe fn get_vm_memory_definition(&self) -> NonNull<VMMemoryDefinition> {
match &self.vm_memory_definition {
VMMemoryDefinitionOwnership::VMOwned(ptr) => *ptr,
VMMemoryDefinitionOwnership::HostOwned(boxed_ptr) => {
NonNull::new_unchecked(boxed_ptr.get())
}
}
}
}
impl Memory for LinearMemory {
fn ty(&self) -> MemoryType {
let minimum = self.size();
let mut out = self.memory;
out.minimum = minimum;
out
}
fn style(&self) -> &MemoryStyle {
&self.style
}
fn size(&self) -> Pages {
unsafe {
let md_ptr = self.get_vm_memory_definition();
let md = md_ptr.as_ref();
Bytes::from(md.current_length).try_into().unwrap()
}
}
fn grow(&self, delta: Pages) -> Result<Pages, MemoryError> {
let mut mmap_guard = self.mmap.lock().unwrap();
let mmap = mmap_guard.borrow_mut();
if delta.0 == 0 {
return Ok(mmap.size);
}
let new_pages = mmap
.size
.checked_add(delta)
.ok_or(MemoryError::CouldNotGrow { current: mmap.size, attempted_delta: delta })?;
let prev_pages = mmap.size;
if let Some(maximum) = self.maximum {
if new_pages > maximum {
return Err(MemoryError::CouldNotGrow {
current: mmap.size,
attempted_delta: delta,
});
}
}
if new_pages >= Pages::max_value() {
return Err(MemoryError::CouldNotGrow { current: mmap.size, attempted_delta: delta });
}
let delta_bytes = delta.bytes().0;
let prev_bytes = prev_pages.bytes().0;
let new_bytes = new_pages.bytes().0;
if new_bytes > mmap.alloc.len() - self.offset_guard_size {
let guard_bytes = self.offset_guard_size;
let request_bytes =
new_bytes.checked_add(guard_bytes).ok_or_else(|| MemoryError::CouldNotGrow {
current: new_pages,
attempted_delta: Bytes(guard_bytes).try_into().unwrap(),
})?;
let mut new_mmap =
Mmap::accessible_reserved(new_bytes, request_bytes).map_err(MemoryError::Region)?;
let copy_len = mmap.alloc.len() - self.offset_guard_size;
new_mmap.as_mut_slice()[..copy_len].copy_from_slice(&mmap.alloc.as_slice()[..copy_len]);
mmap.alloc = new_mmap;
} else if delta_bytes > 0 {
mmap.alloc.make_accessible(prev_bytes, delta_bytes).map_err(MemoryError::Region)?;
}
mmap.size = new_pages;
unsafe {
let mut md_ptr = self.get_vm_memory_definition();
let md = md_ptr.as_mut();
md.current_length = new_pages.bytes().0;
md.base = mmap.alloc.as_mut_ptr() as _;
}
Ok(prev_pages)
}
fn vmmemory(&self) -> NonNull<VMMemoryDefinition> {
let _mmap_guard = self.mmap.lock().unwrap();
unsafe { self.get_vm_memory_definition() }
}
}