use crate::store::{StoreData, StoreOpaque, Stored};
use crate::trampoline::generate_memory_export;
use crate::Trap;
use crate::{AsContext, AsContextMut, Engine, MemoryType, StoreContext, StoreContextMut};
use anyhow::{bail, Result};
use std::cell::UnsafeCell;
use std::convert::TryFrom;
use std::slice;
use std::time::Instant;
use wasmtime_environ::MemoryPlan;
use wasmtime_runtime::{RuntimeLinearMemory, VMMemoryImport};
pub use wasmtime_runtime::WaitResult;
#[derive(Debug)]
#[non_exhaustive]
pub struct MemoryAccessError {
_private: (),
}
impl std::fmt::Display for MemoryAccessError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "out of bounds memory access")
}
}
impl std::error::Error for MemoryAccessError {}
#[derive(Copy, Clone, Debug)]
#[repr(transparent)] pub struct Memory(Stored<wasmtime_runtime::ExportMemory>);
impl Memory {
pub fn new(mut store: impl AsContextMut, ty: MemoryType) -> Result<Memory> {
Self::_new(store.as_context_mut().0, ty)
}
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
#[cfg(feature = "async")]
pub async fn new_async<T>(
mut store: impl AsContextMut<Data = T>,
ty: MemoryType,
) -> Result<Memory>
where
T: Send,
{
let mut store = store.as_context_mut();
assert!(
store.0.async_support(),
"cannot use `new_async` without enabling async support on the config"
);
store.on_fiber(|store| Self::_new(store.0, ty)).await?
}
fn _new(store: &mut StoreOpaque, ty: MemoryType) -> Result<Memory> {
unsafe {
let export = generate_memory_export(store, &ty, None)?;
Ok(Memory::from_wasmtime_memory(export, store))
}
}
pub fn ty(&self, store: impl AsContext) -> MemoryType {
let store = store.as_context();
let ty = &store[self.0].memory.memory;
MemoryType::from_wasmtime_memory(&ty)
}
pub fn read(
&self,
store: impl AsContext,
offset: usize,
buffer: &mut [u8],
) -> Result<(), MemoryAccessError> {
let store = store.as_context();
let slice = self
.data(&store)
.get(offset..)
.and_then(|s| s.get(..buffer.len()))
.ok_or(MemoryAccessError { _private: () })?;
buffer.copy_from_slice(slice);
Ok(())
}
pub fn write(
&self,
mut store: impl AsContextMut,
offset: usize,
buffer: &[u8],
) -> Result<(), MemoryAccessError> {
let mut context = store.as_context_mut();
self.data_mut(&mut context)
.get_mut(offset..)
.and_then(|s| s.get_mut(..buffer.len()))
.ok_or(MemoryAccessError { _private: () })?
.copy_from_slice(buffer);
Ok(())
}
pub fn data<'a, T: 'a>(&self, store: impl Into<StoreContext<'a, T>>) -> &'a [u8] {
unsafe {
let store = store.into();
let definition = &*store[self.0].definition;
debug_assert!(!self.ty(store).is_shared());
slice::from_raw_parts(definition.base, definition.current_length())
}
}
pub fn data_mut<'a, T: 'a>(&self, store: impl Into<StoreContextMut<'a, T>>) -> &'a mut [u8] {
unsafe {
let store = store.into();
let definition = &*store[self.0].definition;
debug_assert!(!self.ty(store).is_shared());
slice::from_raw_parts_mut(definition.base, definition.current_length())
}
}
pub fn data_and_store_mut<'a, T: 'a>(
&self,
store: impl Into<StoreContextMut<'a, T>>,
) -> (&'a mut [u8], &'a mut T) {
unsafe {
let mut store = store.into();
let data = &mut *(store.data_mut() as *mut T);
(self.data_mut(store), data)
}
}
pub fn data_ptr(&self, store: impl AsContext) -> *mut u8 {
unsafe { (*store.as_context()[self.0].definition).base }
}
pub fn data_size(&self, store: impl AsContext) -> usize {
self.internal_data_size(store.as_context().0)
}
pub(crate) fn internal_data_size(&self, store: &StoreOpaque) -> usize {
unsafe { (*store[self.0].definition).current_length() }
}
pub fn size(&self, store: impl AsContext) -> u64 {
self.internal_size(store.as_context().0)
}
pub(crate) fn internal_size(&self, store: &StoreOpaque) -> u64 {
(self.internal_data_size(store) / wasmtime_environ::WASM_PAGE_SIZE as usize) as u64
}
pub fn grow(&self, mut store: impl AsContextMut, delta: u64) -> Result<u64> {
let store = store.as_context_mut().0;
let mem = self.wasmtime_memory(store);
unsafe {
match (*mem).grow(delta, Some(store))? {
Some(size) => {
let vm = (*mem).vmmemory();
*store[self.0].definition = vm;
Ok(u64::try_from(size).unwrap() / u64::from(wasmtime_environ::WASM_PAGE_SIZE))
}
None => bail!("failed to grow memory by `{}`", delta),
}
}
}
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
#[cfg(feature = "async")]
pub async fn grow_async<T>(
&self,
mut store: impl AsContextMut<Data = T>,
delta: u64,
) -> Result<u64>
where
T: Send,
{
let mut store = store.as_context_mut();
assert!(
store.0.async_support(),
"cannot use `grow_async` without enabling async support on the config"
);
store.on_fiber(|store| self.grow(store, delta)).await?
}
fn wasmtime_memory(&self, store: &mut StoreOpaque) -> *mut wasmtime_runtime::Memory {
unsafe {
let export = &store[self.0];
let mut handle = wasmtime_runtime::InstanceHandle::from_vmctx(export.vmctx);
handle.get_defined_memory(export.index)
}
}
pub(crate) unsafe fn from_wasmtime_memory(
wasmtime_export: wasmtime_runtime::ExportMemory,
store: &mut StoreOpaque,
) -> Memory {
Memory(store.store_data_mut().insert(wasmtime_export))
}
pub(crate) fn wasmtime_ty<'a>(&self, store: &'a StoreData) -> &'a wasmtime_environ::Memory {
&store[self.0].memory.memory
}
pub(crate) fn vmimport(&self, store: &StoreOpaque) -> wasmtime_runtime::VMMemoryImport {
let export = &store[self.0];
wasmtime_runtime::VMMemoryImport {
from: export.definition,
vmctx: export.vmctx,
index: export.index,
}
}
pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
store.store_data().contains(self.0)
}
}
pub unsafe trait LinearMemory: Send + Sync + 'static {
fn byte_size(&self) -> usize;
fn maximum_byte_size(&self) -> Option<usize>;
fn grow_to(&mut self, new_size: usize) -> Result<()>;
fn as_ptr(&self) -> *mut u8;
}
pub unsafe trait MemoryCreator: Send + Sync {
fn new_memory(
&self,
ty: MemoryType,
minimum: usize,
maximum: Option<usize>,
reserved_size_in_bytes: Option<usize>,
guard_size_in_bytes: usize,
) -> Result<Box<dyn LinearMemory>, String>;
}
#[derive(Clone)]
pub struct SharedMemory(wasmtime_runtime::SharedMemory, Engine);
impl SharedMemory {
pub fn new(engine: &Engine, ty: MemoryType) -> Result<Self> {
if !ty.is_shared() {
bail!("shared memory must have the `shared` flag enabled on its memory type")
}
debug_assert!(ty.maximum().is_some());
let tunables = &engine.config().tunables;
let plan = MemoryPlan::for_memory(ty.wasmtime_memory().clone(), tunables);
let memory = wasmtime_runtime::SharedMemory::new(plan)?;
Ok(Self(memory, engine.clone()))
}
pub fn ty(&self) -> MemoryType {
MemoryType::from_wasmtime_memory(&self.0.ty())
}
pub fn size(&self) -> u64 {
(self.data_size() / wasmtime_environ::WASM_PAGE_SIZE as usize) as u64
}
pub fn data_size(&self) -> usize {
self.0.byte_size()
}
pub fn data(&self) -> &[UnsafeCell<u8>] {
unsafe {
let definition = &*self.0.vmmemory_ptr();
slice::from_raw_parts_mut(definition.base.cast(), definition.current_length())
}
}
pub fn grow(&self, delta: u64) -> Result<u64> {
match self.0.grow(delta, None)? {
Some((old_size, _new_size)) => {
Ok(u64::try_from(old_size).unwrap() / u64::from(wasmtime_environ::WASM_PAGE_SIZE))
}
None => bail!("failed to grow memory by `{}`", delta),
}
}
pub fn atomic_notify(&self, addr: u64, count: u32) -> Result<u32, Trap> {
self.0.atomic_notify(addr, count)
}
pub fn atomic_wait32(
&self,
addr: u64,
expected: u32,
timeout: Option<Instant>,
) -> Result<WaitResult, Trap> {
self.0.atomic_wait32(addr, expected, timeout)
}
pub fn atomic_wait64(
&self,
addr: u64,
expected: u64,
timeout: Option<Instant>,
) -> Result<WaitResult, Trap> {
self.0.atomic_wait64(addr, expected, timeout)
}
pub(crate) fn engine(&self) -> &Engine {
&self.1
}
pub(crate) fn vmimport(&self, store: &mut StoreOpaque) -> wasmtime_runtime::VMMemoryImport {
let export_memory = generate_memory_export(store, &self.ty(), Some(&self.0)).unwrap();
VMMemoryImport {
from: export_memory.definition,
vmctx: export_memory.vmctx,
index: export_memory.index,
}
}
pub(crate) unsafe fn from_wasmtime_memory(
wasmtime_export: wasmtime_runtime::ExportMemory,
store: &mut StoreOpaque,
) -> Self {
let mut handle = wasmtime_runtime::InstanceHandle::from_vmctx(wasmtime_export.vmctx);
let memory = handle
.get_defined_memory(wasmtime_export.index)
.as_mut()
.unwrap();
let shared_memory = memory
.as_shared_memory()
.expect("unable to convert from a shared memory")
.clone();
Self(shared_memory, store.engine().clone())
}
}
impl std::fmt::Debug for SharedMemory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SharedMemory").finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn respect_tunables() {
let mut cfg = Config::new();
cfg.static_memory_maximum_size(0)
.dynamic_memory_guard_size(0);
let mut store = Store::new(&Engine::new(&cfg).unwrap(), ());
let ty = MemoryType::new(1, None);
let mem = Memory::new(&mut store, ty).unwrap();
let store = store.as_context();
assert_eq!(store[mem.0].memory.offset_guard_size, 0);
match &store[mem.0].memory.style {
wasmtime_environ::MemoryStyle::Dynamic { .. } => {}
other => panic!("unexpected style {:?}", other),
}
}
}