All IO operates are now zero copy when on non-browsers while browsers still copy bytes

This commit is contained in:
Johnathan Sharratt
2023-03-03 16:04:09 +11:00
parent 06524345c7
commit f1c31c65ca
14 changed files with 581 additions and 330 deletions

223
lib/api/src/access.rs Normal file
View File

@@ -0,0 +1,223 @@
use std::mem::MaybeUninit;
use crate::{WasmRef, WasmSlice};
pub(super) enum SliceCow<'a, T> {
#[allow(dead_code)]
Borrowed(&'a mut [T]),
#[allow(dead_code)]
Owned(Vec<T>, bool),
}
impl<'a, T> AsRef<[T]> for SliceCow<'a, T> {
fn as_ref(&self) -> &[T] {
match self {
Self::Borrowed(buf) => buf.as_ref(),
Self::Owned(buf, _) => buf.as_ref(),
}
}
}
impl<'a, T> AsMut<[T]> for SliceCow<'a, T> {
fn as_mut(&mut self) -> &mut [T] {
// Note: Zero padding is not required here as its a typed copy which does
// not leak the bytes into the memory
// https://stackoverflow.com/questions/61114026/does-stdptrwrite-transfer-the-uninitialized-ness-of-the-bytes-it-writes
match self {
Self::Borrowed(buf) => buf,
Self::Owned(buf, modified) => {
*modified = true;
buf.as_mut()
}
}
}
}
/// Provides direct memory access to a piece of memory that
/// is owned by WASM
pub struct WasmSliceAccess<'a, T>
where
T: wasmer_types::ValueType,
{
pub(super) slice: WasmSlice<'a, T>,
pub(super) buf: SliceCow<'a, T>,
}
impl<'a, T> AsRef<[T]> for WasmSliceAccess<'a, T>
where
T: wasmer_types::ValueType,
{
fn as_ref(&self) -> &[T] {
self.buf.as_ref()
}
}
impl<'a, T> AsMut<[T]> for WasmSliceAccess<'a, T>
where
T: wasmer_types::ValueType,
{
fn as_mut(&mut self) -> &mut [T] {
self.buf.as_mut()
}
}
impl<'a, T> WasmSliceAccess<'a, T>
where
T: wasmer_types::ValueType,
{
/// Returns an iterator of all the elements in the slice
pub fn iter(&'a self) -> std::slice::Iter<'a, T> {
self.as_ref().iter()
}
/// Returns an iterator of all the elements in the slice
pub fn iter_mut(&'a mut self) -> std::slice::IterMut<'a, T> {
self.buf.as_mut().iter_mut()
}
/// Number of elements in this slice
pub fn len(&self) -> usize {
self.buf.as_ref().len()
}
}
impl<'a> WasmSliceAccess<'a, u8> {
/// Writes to the address pointed to by this `WasmPtr` in a memory.
#[inline]
pub fn copy_from_slice(&mut self, src: &[u8]) {
let dst = self.buf.as_mut();
dst.copy_from_slice(src);
}
}
impl<'a, T> Drop for WasmSliceAccess<'a, T>
where
T: wasmer_types::ValueType,
{
fn drop(&mut self) {
if let SliceCow::Owned(buf, modified) = &self.buf {
if *modified == true {
self.slice.write_slice(buf.as_ref()).ok();
}
}
}
}
pub(super) enum RefCow<'a, T> {
#[allow(dead_code)]
Borrowed(&'a mut T),
#[allow(dead_code)]
Owned(T, bool),
}
impl<'a, T> AsRef<T> for RefCow<'a, T> {
fn as_ref(&self) -> &T {
match self {
Self::Borrowed(val) => *val,
Self::Owned(val, _) => val,
}
}
}
impl<'a, T> AsMut<T> for RefCow<'a, T> {
fn as_mut(&mut self) -> &mut T {
// Note: Zero padding is not required here as its a typed copy which does
// not leak the bytes into the memory
// https://stackoverflow.com/questions/61114026/does-stdptrwrite-transfer-the-uninitialized-ness-of-the-bytes-it-writes
match self {
Self::Borrowed(val) => *val,
Self::Owned(val, modified) => {
*modified = true;
val
}
}
}
}
/// Provides direct memory access to a piece of memory that
/// is owned by WASM
pub struct WasmRefAccess<'a, T>
where
T: wasmer_types::ValueType,
{
pub(super) ptr: WasmRef<'a, T>,
pub(super) buf: RefCow<'a, T>,
}
impl<'a, T> AsRef<T> for WasmRefAccess<'a, T>
where
T: wasmer_types::ValueType,
{
fn as_ref(&self) -> &T {
self.buf.as_ref()
}
}
impl<'a, T> AsMut<T> for WasmRefAccess<'a, T>
where
T: wasmer_types::ValueType,
{
fn as_mut(&mut self) -> &mut T {
self.buf.as_mut()
}
}
impl<'a, T> WasmRefAccess<'a, T>
where
T: wasmer_types::ValueType,
{
/// Reads the address pointed to by this `WasmPtr` in a memory.
#[inline]
pub fn read(&self) -> T
where
T: Clone,
{
self.as_ref().clone()
}
/// Writes to the address pointed to by this `WasmPtr` in a memory.
#[inline]
pub fn write(&mut self, val: T) {
// Note: Zero padding is not required here as its a typed copy which does
// not leak the bytes into the memory
// https://stackoverflow.com/questions/61114026/does-stdptrwrite-transfer-the-uninitialized-ness-of-the-bytes-it-writes
*(self.as_mut()) = val;
}
}
impl<'a, T> Drop for WasmRefAccess<'a, T>
where
T: wasmer_types::ValueType,
{
fn drop(&mut self) {
if let RefCow::Owned(val, modified) = &self.buf {
if *modified == true {
self.ptr.write(*val).ok();
}
}
}
}
impl<'a, T> WasmSliceAccess<'a, T>
where
T: wasmer_types::ValueType,
{
/// Returns a mutable slice that is not yet initialized
pub fn as_mut_uninit(&mut self) -> &mut [MaybeUninit<T>] {
let ret: &mut [T] = self.buf.as_mut();
let ret: &mut [MaybeUninit<T>] = unsafe { std::mem::transmute(ret) };
ret
}
}
impl<'a, T> WasmRefAccess<'a, T>
where
T: wasmer_types::ValueType,
{
/// Returns a reference to an unitialized reference to this value
pub fn as_mut_uninit(&mut self) -> &mut MaybeUninit<T> {
let ret: &mut T = self.buf.as_mut();
let ret: &mut MaybeUninit<T> = unsafe { std::mem::transmute(ret) };
ret
}
}

View File

@@ -405,3 +405,29 @@ impl<'a, T: ValueType> DoubleEndedIterator for WasmSliceIter<'a, T> {
}
impl<'a, T: ValueType> ExactSizeIterator for WasmSliceIter<'a, T> {}
impl<'a, T> WasmSliceAccess<'a, T>
where
T: wasmer_types::ValueType,
{
fn new(slice: WasmSlice<'a, T>) -> Result<Self, MemoryAccessError> {
let buf = slice.read_to_vec()?;
Ok(Self {
slice,
buf: SliceCow::Owned(buf),
})
}
}
impl<'a, T> WasmRefAccess<'a, T>
where
T: wasmer_types::ValueType,
{
fn new(ptr: WasmRef<'a, T>) -> Result<Self, MemoryAccessError> {
let val = slice.read()?;
Ok(Self {
ptr,
buf: RefCow::Owned(val),
})
}
}

View File

@@ -441,5 +441,7 @@ mod js;
#[cfg(feature = "js")]
pub use js::*;
mod access;
mod into_bytes;
pub use access::WasmSliceAccess;
pub use into_bytes::IntoBytes;

View File

@@ -1,4 +1,7 @@
use crate::RuntimeError;
use crate::{
access::{RefCow, SliceCow, WasmRefAccess},
RuntimeError, WasmSliceAccess,
};
#[allow(unused_imports)]
use crate::{Memory, Memory32, Memory64, MemorySize, MemoryView, WasmPtr};
use std::{
@@ -102,26 +105,19 @@ impl<'a, T: ValueType> WasmRef<'a, T> {
/// Reads the location pointed to by this `WasmRef`.
#[inline]
pub fn read(self) -> Result<T, MemoryAccessError> {
let mut out = MaybeUninit::uninit();
let buf =
unsafe { slice::from_raw_parts_mut(out.as_mut_ptr() as *mut u8, mem::size_of::<T>()) };
self.buffer.read(self.offset, buf)?;
Ok(unsafe { out.assume_init() })
Ok(self.access()?.read())
}
/// Writes to the location pointed to by this `WasmRef`.
#[inline]
pub fn write(self, val: T) -> Result<(), MemoryAccessError> {
let mut data = MaybeUninit::new(val);
let data = unsafe {
slice::from_raw_parts_mut(
data.as_mut_ptr() as *mut MaybeUninit<u8>,
mem::size_of::<T>(),
)
};
val.zero_padding_bytes(data);
let data = unsafe { slice::from_raw_parts(data.as_ptr() as *const _, data.len()) };
self.buffer.write(self.offset, data)
Ok(self.access()?.write(val))
}
/// Gains direct access to the memory of this slice
#[inline]
pub fn access(self) -> Result<WasmRefAccess<'a, T>, MemoryAccessError> {
WasmRefAccess::new(self)
}
}
@@ -240,6 +236,12 @@ impl<'a, T: ValueType> WasmSlice<'a, T> {
WasmSliceIter { slice: self }
}
/// Gains direct access to the memory of this slice
#[inline]
pub fn access(self) -> Result<WasmSliceAccess<'a, T>, MemoryAccessError> {
WasmSliceAccess::new(self)
}
/// Reads an element of this slice.
#[inline]
pub fn read(self, idx: u64) -> Result<T, MemoryAccessError> {
@@ -404,3 +406,66 @@ impl<'a, T: ValueType> DoubleEndedIterator for WasmSliceIter<'a, T> {
}
impl<'a, T: ValueType> ExactSizeIterator for WasmSliceIter<'a, T> {}
impl<'a, T> WasmSliceAccess<'a, T>
where
T: wasmer_types::ValueType,
{
fn new(slice: WasmSlice<'a, T>) -> Result<Self, MemoryAccessError> {
let total_len = slice
.len
.checked_mul(mem::size_of::<T>() as u64)
.ok_or(MemoryAccessError::Overflow)?;
let end = slice
.offset
.checked_add(total_len)
.ok_or(MemoryAccessError::Overflow)?;
if end > slice.buffer.len as u64 {
#[cfg(feature = "tracing")]
warn!(
"attempted to read ({} bytes) beyond the bounds of the memory view ({} > {})",
total_len, end, slice.buffer.len
);
return Err(MemoryAccessError::HeapOutOfBounds);
}
let buf = unsafe {
let buf_ptr: *mut u8 = slice.buffer.base.add(slice.offset as usize);
let buf_ptr: *mut T = std::mem::transmute(buf_ptr);
std::slice::from_raw_parts_mut(buf_ptr, slice.len as usize)
};
Ok(Self {
slice,
buf: SliceCow::Borrowed(buf),
})
}
}
impl<'a, T> WasmRefAccess<'a, T>
where
T: wasmer_types::ValueType,
{
fn new(ptr: WasmRef<'a, T>) -> Result<Self, MemoryAccessError> {
let total_len = mem::size_of::<T>() as u64;
let end = ptr
.offset
.checked_add(total_len)
.ok_or(MemoryAccessError::Overflow)?;
if end > ptr.buffer.len as u64 {
#[cfg(feature = "tracing")]
warn!(
"attempted to read ({} bytes) beyond the bounds of the memory view ({} > {})",
total_len, end, ptr.buffer.len
);
return Err(MemoryAccessError::HeapOutOfBounds);
}
let val = unsafe {
let val_ptr: *mut u8 = ptr.buffer.base.add(ptr.offset as usize);
let val_ptr: *mut T = std::mem::transmute(val_ptr);
&mut *val_ptr
};
Ok(Self {
ptr,
buf: RefCow::Borrowed(val),
})
}
}

View File

@@ -1,3 +1,4 @@
use crate::access::WasmRefAccess;
use crate::sys::{externals::MemoryView, FromToNativeWasmType};
use crate::NativeWasmTypeInto;
use crate::{MemoryAccessError, WasmRef, WasmSlice};
@@ -193,6 +194,15 @@ impl<T: ValueType, M: MemorySize> WasmPtr<T, M> {
}
Ok(vec)
}
/// Creates a `WasmAccess`
#[inline]
pub fn access<'a>(
&self,
view: &'a MemoryView,
) -> Result<WasmRefAccess<'a, T>, MemoryAccessError> {
self.deref(view).access()
}
}
impl<M: MemorySize> WasmPtr<u8, M> {