use crate::js::export::VMMemory;
use crate::js::exports::{ExportError, Exportable};
use crate::js::externals::Extern;
use crate::js::store::{AsStoreMut, AsStoreRef, InternalStoreHandle, StoreHandle, StoreObjects};
use crate::js::{MemoryAccessError, MemoryType};
use std::convert::TryInto;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::slice;
use thiserror::Error;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasmer_types::{Bytes, Pages};
#[derive(Error, Debug, Clone, PartialEq, Hash)]
pub enum MemoryError {
#[error("The memory could not grow: current size {} pages, requested increase: {} pages", current.0, attempted_delta.0)]
CouldNotGrow {
current: Pages,
attempted_delta: Pages,
},
#[error("A user-defined error occurred: {0}")]
Generic(String),
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = WebAssembly, extends = js_sys::Object, typescript_type = "WebAssembly.Memory")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub type JSMemory;
#[wasm_bindgen(catch, method, js_namespace = WebAssembly)]
pub fn grow(this: &JSMemory, pages: u32) -> Result<u32, JsValue>;
}
#[derive(Debug, Clone)]
pub struct Memory {
pub(crate) handle: StoreHandle<VMMemory>,
#[allow(dead_code)]
view: js_sys::Uint8Array,
}
unsafe impl Send for Memory {}
unsafe impl Sync for Memory {}
impl Memory {
pub fn new(store: &mut impl AsStoreMut, ty: MemoryType) -> Result<Self, MemoryError> {
let descriptor = js_sys::Object::new();
js_sys::Reflect::set(&descriptor, &"initial".into(), &ty.minimum.0.into()).unwrap();
if let Some(max) = ty.maximum {
js_sys::Reflect::set(&descriptor, &"maximum".into(), &max.0.into()).unwrap();
}
js_sys::Reflect::set(&descriptor, &"shared".into(), &ty.shared.into()).unwrap();
let js_memory = js_sys::WebAssembly::Memory::new(&descriptor)
.map_err(|_e| MemoryError::Generic("Error while creating the memory".to_owned()))?;
let vm_memory = VMMemory::new(js_memory, ty);
Ok(Self::from_vm_export(store, vm_memory))
}
pub fn ty(&self, store: &impl AsStoreRef) -> MemoryType {
self.handle.get(store.as_store_ref().objects()).ty
}
#[doc(hidden)]
pub fn data_ptr(&self) -> *mut u8 {
unimplemented!("direct data pointer access is not possible in JavaScript");
}
pub fn data_size(&self, store: &impl AsStoreRef) -> u64 {
js_sys::Reflect::get(
&self
.handle
.get(store.as_store_ref().objects())
.memory
.buffer(),
&"byteLength".into(),
)
.unwrap()
.as_f64()
.unwrap() as _
}
pub fn size(&self, store: &impl AsStoreRef) -> Pages {
let bytes = js_sys::Reflect::get(
&self
.handle
.get(store.as_store_ref().objects())
.memory
.buffer(),
&"byteLength".into(),
)
.unwrap()
.as_f64()
.unwrap() as u64;
Bytes(bytes as usize).try_into().unwrap()
}
pub fn grow<IntoPages>(
&self,
store: &mut impl AsStoreMut,
delta: IntoPages,
) -> Result<Pages, MemoryError>
where
IntoPages: Into<Pages>,
{
let pages = delta.into();
let js_memory = &self.handle.get_mut(store.objects_mut()).memory;
let our_js_memory: &JSMemory = JsCast::unchecked_from_js_ref(js_memory);
let new_pages = our_js_memory.grow(pages.0).map_err(|err| {
if err.is_instance_of::<js_sys::RangeError>() {
MemoryError::CouldNotGrow {
current: self.size(&store.as_store_ref()),
attempted_delta: pages,
}
} else {
MemoryError::Generic(err.as_string().unwrap())
}
})?;
Ok(Pages(new_pages))
}
#[doc(hidden)]
pub fn uint8view(&self, store: &impl AsStoreRef) -> js_sys::Uint8Array {
js_sys::Uint8Array::new(
&self
.handle
.get(store.as_store_ref().objects())
.memory
.buffer(),
)
}
pub(crate) fn buffer<'a>(&'a self, _store: &'a impl AsStoreRef) -> MemoryBuffer<'a> {
MemoryBuffer {
base: &self.view as *const _ as *mut _,
marker: PhantomData,
}
}
pub(crate) fn from_vm_export(store: &mut impl AsStoreMut, vm_memory: VMMemory) -> Self {
let view = js_sys::Uint8Array::new(&vm_memory.memory.buffer());
Self {
handle: StoreHandle::new(store.objects_mut(), vm_memory),
view,
}
}
pub(crate) fn from_vm_extern(
store: &mut impl AsStoreMut,
internal: InternalStoreHandle<VMMemory>,
) -> Self {
let view =
js_sys::Uint8Array::new(&internal.get(store.as_store_ref().objects()).memory.buffer());
Self {
handle: unsafe {
StoreHandle::from_internal(store.as_store_ref().objects().id(), internal)
},
view,
}
}
pub fn read(
&self,
_store: &impl AsStoreRef,
offset: u64,
data: &mut [u8],
) -> Result<(), MemoryAccessError> {
let view = &self.view;
let offset: u32 = offset.try_into().map_err(|_| MemoryAccessError::Overflow)?;
let len: u32 = data
.len()
.try_into()
.map_err(|_| MemoryAccessError::Overflow)?;
let end = offset.checked_add(len).ok_or(MemoryAccessError::Overflow)?;
if end > view.length() {
Err(MemoryAccessError::HeapOutOfBounds)?;
}
view.subarray(offset, end).copy_to(data);
Ok(())
}
pub fn read_uninit<'a>(
&self,
_store: &impl AsStoreRef,
offset: u64,
buf: &'a mut [MaybeUninit<u8>],
) -> Result<&'a mut [u8], MemoryAccessError> {
let view = &self.view;
let offset: u32 = offset.try_into().map_err(|_| MemoryAccessError::Overflow)?;
let len: u32 = buf
.len()
.try_into()
.map_err(|_| MemoryAccessError::Overflow)?;
let end = offset.checked_add(len).ok_or(MemoryAccessError::Overflow)?;
if end > view.length() {
Err(MemoryAccessError::HeapOutOfBounds)?;
}
for elem in buf.iter_mut() {
*elem = MaybeUninit::new(0);
}
let buf = unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len()) };
view.subarray(offset, end).copy_to(buf);
Ok(buf)
}
pub fn write(
&self,
_store: &mut impl AsStoreMut,
offset: u64,
data: &[u8],
) -> Result<(), MemoryAccessError> {
let offset: u32 = offset.try_into().map_err(|_| MemoryAccessError::Overflow)?;
let len: u32 = data
.len()
.try_into()
.map_err(|_| MemoryAccessError::Overflow)?;
let view = &self.view;
let end = offset.checked_add(len).ok_or(MemoryAccessError::Overflow)?;
if end > view.length() {
Err(MemoryAccessError::HeapOutOfBounds)?;
}
view.subarray(offset, end).copy_from(data);
Ok(())
}
pub fn is_from_store(&self, store: &impl AsStoreRef) -> bool {
self.handle.store_id() == store.as_store_ref().objects().id()
}
}
impl<'a> Exportable<'a> for Memory {
fn get_self_from_extern(_extern: &'a Extern) -> Result<&'a Self, ExportError> {
match _extern {
Extern::Memory(memory) => Ok(memory),
_ => Err(ExportError::IncompatibleType),
}
}
}
#[derive(Copy, Clone)]
pub(crate) struct MemoryBuffer<'a> {
base: *mut js_sys::Uint8Array,
marker: PhantomData<(&'a Memory, &'a StoreObjects)>,
}
impl<'a> MemoryBuffer<'a> {
pub(crate) fn read(&self, offset: u64, buf: &mut [u8]) -> Result<(), MemoryAccessError> {
let end = offset
.checked_add(buf.len() as u64)
.ok_or(MemoryAccessError::Overflow)?;
let view = unsafe { &*(self.base) };
if end > view.length().into() {
return Err(MemoryAccessError::HeapOutOfBounds);
}
view.subarray(offset as _, end as _)
.copy_to(unsafe { &mut slice::from_raw_parts_mut(buf.as_mut_ptr(), buf.len()) });
Ok(())
}
pub(crate) fn read_uninit<'b>(
&self,
offset: u64,
buf: &'b mut [MaybeUninit<u8>],
) -> Result<&'b mut [u8], MemoryAccessError> {
let end = offset
.checked_add(buf.len() as u64)
.ok_or(MemoryAccessError::Overflow)?;
let view = unsafe { &*(self.base) };
if end > view.length().into() {
return Err(MemoryAccessError::HeapOutOfBounds);
}
let buf_ptr = buf.as_mut_ptr() as *mut u8;
view.subarray(offset as _, end as _)
.copy_to(unsafe { &mut slice::from_raw_parts_mut(buf_ptr, buf.len()) });
Ok(unsafe { slice::from_raw_parts_mut(buf_ptr, buf.len()) })
}
pub(crate) fn write(&self, offset: u64, data: &[u8]) -> Result<(), MemoryAccessError> {
let end = offset
.checked_add(data.len() as u64)
.ok_or(MemoryAccessError::Overflow)?;
let view = unsafe { &mut *(self.base) };
if end > view.length().into() {
return Err(MemoryAccessError::HeapOutOfBounds);
}
view.subarray(offset as _, end as _).copy_from(data);
Ok(())
}
}