Skip to content

Commit

Permalink
Implement the Wasm custom-page-sizes proposal (#1349)
Browse files Browse the repository at this point in the history
* add wasm_custom_page_sizes API to Config

* update docs for custom_page_sizes setter

* wip: implement custom-page-sizes

This currently fails some Wasm spec tests.
Also we want to enable Wasm custom-page-sizes spec tests.

* fix clippy doc warnings

* no longer use iter::repeat_n

This API has been stabilized in Rust 1.82 while our MSRV is not yet there.

* fix bug in MemoryEntity::grow

* fix value of absolute_max

* change error message to be less confusing

* fix MemoryEntity::new_impl

* add custom-page-sizes Wasm spec tests

* fix From wasmparser impl for MemoryType

Now properly uses the input page_size_log2 parameter and uses the new MemoryTypeBuilder.

* fix linking of memories with different page sizes

* rename MemoryType::initial_pages -> minimum

* rename MemoryType::maximum_pages -> maximum

* add MemoryTypeBuilder::new method

Improves Wasmtime API mirroring.
  • Loading branch information
Robbepop authored Feb 1, 2025
1 parent 4c779fc commit 9447890
Show file tree
Hide file tree
Showing 9 changed files with 437 additions and 186 deletions.
4 changes: 2 additions & 2 deletions crates/c_api/src/types/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ impl wasm_memorytype_t {

impl CMemoryType {
pub(crate) fn new(ty: MemoryType) -> CMemoryType {
let min: u32 = ty.initial_pages().into();
let max: u32 = ty.maximum_pages().map(Into::into).unwrap_or(u32::MAX);
let min: u32 = ty.minimum();
let max: u32 = ty.maximum().unwrap_or(u32::MAX);
CMemoryType {
ty,
limits: wasm_limits_t { min, max },
Expand Down
13 changes: 13 additions & 0 deletions crates/wasmi/src/engine/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ impl Config {
features.set(WasmFeatures::TAIL_CALL, true);
features.set(WasmFeatures::EXTENDED_CONST, true);
features.set(WasmFeatures::FLOATS, true);
features.set(WasmFeatures::CUSTOM_PAGE_SIZES, false);
features
}

Expand Down Expand Up @@ -321,6 +322,18 @@ impl Config {
self
}

/// Enable or disable the [`custom-page-sizes`] Wasm proposal for the [`Config`].
///
/// # Note
///
/// Disabled by default.
///
/// [`custom-page-sizes`]: https://github.com/WebAssembly/custom-page-sizes
pub fn wasm_custom_page_sizes(&mut self, enable: bool) -> &mut Self {
self.features.set(WasmFeatures::CUSTOM_PAGE_SIZES, enable);
self
}

/// Enable or disable Wasm floating point (`f32` and `f64`) instructions and types.
///
/// Enabled by default.
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ pub use self::{
instance::{Export, ExportsIter, Extern, ExternType, Instance},
limits::{ResourceLimiter, StoreLimits, StoreLimitsBuilder},
linker::{state, Linker, LinkerBuilder},
memory::{Memory, MemoryType},
memory::{Memory, MemoryType, MemoryTypeBuilder},
module::{
CustomSection,
CustomSectionsIter,
Expand Down
117 changes: 68 additions & 49 deletions crates/wasmi/src/memory/buffer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use alloc::{slice, vec, vec::Vec};
use core::mem::ManuallyDrop;
use crate::memory::MemoryError;
use alloc::{slice, vec::Vec};
use core::{iter, mem::ManuallyDrop};

/// A byte buffer implementation.
///
Expand Down Expand Up @@ -67,38 +68,44 @@ fn vec_into_raw_parts(vec: Vec<u8>) -> (*mut u8, usize, usize) {
}

impl ByteBuffer {
/// Creates a new byte buffer with the given initial length.
/// Creates a new byte buffer with the given initial `size` in bytes.
///
/// # Panics
/// # Errors
///
/// If there is not enough memory to initialize `initial_len` bytes.
pub fn new(initial_len: usize) -> Self {
let vec = vec![0x00_u8; initial_len];
/// If the requested amount of heap bytes could not be allocated.
pub fn new(size: usize) -> Result<Self, MemoryError> {
let mut vec = Vec::new();
if vec.try_reserve(size).is_err() {
return Err(MemoryError::OutOfBoundsAllocation);
};
vec.extend(iter::repeat(0x00_u8).take(size));
let (ptr, len, capacity) = vec_into_raw_parts(vec);
Self {
Ok(Self {
ptr,
len,
capacity,
is_static: false,
}
})
}

/// Creates a new byte buffer with the given initial length.
/// Creates a new static byte buffer with the given `size` in bytes.
///
/// This will zero all the bytes in `buffer[0..initial_len`].
///
/// # Panics
/// # Errors
///
/// If `initial_len` is greater than the length of `buffer`.
pub fn new_static(buffer: &'static mut [u8], initial_len: usize) -> Self {
assert!(initial_len <= buffer.len());
buffer[..initial_len].fill(0x00_u8);
Self {
/// If `size` is greater than the length of `buffer`.
pub fn new_static(buffer: &'static mut [u8], size: usize) -> Result<Self, MemoryError> {
let Some(bytes) = buffer.get_mut(..size) else {
return Err(MemoryError::InvalidStaticBufferSize);
};
bytes.fill(0x00_u8);
Ok(Self {
ptr: buffer.as_mut_ptr(),
len: initial_len,
len: size,
capacity: buffer.len(),
is_static: true,
}
})
}

/// Grows the byte buffer to the given `new_size`.
Expand All @@ -108,28 +115,41 @@ impl ByteBuffer {
/// # Panics
///
/// - If the current size of the [`ByteBuffer`] is larger than `new_size`.
/// - If backed by static buffer and `new_size` is larger than it's capacity.
pub fn grow(&mut self, new_size: usize) {
assert!(new_size >= self.len());
///
/// # Errors
///
/// - If it is not possible to grow the [`ByteBuffer`] to `new_size`.
/// - `vec`: If the system allocator ran out of memory to allocate.
/// - `static`: If `new_size` is larger than it's the static buffer capacity.
pub fn grow(&mut self, new_size: usize) -> Result<(), MemoryError> {
assert!(self.len() <= new_size);
match self.get_vec() {
Some(mut vec) => {
// Case: the byte buffer is backed by a `Vec<u8>`.
vec.resize(new_size, 0x00_u8);
let (ptr, len, capacity) = vec_into_raw_parts(vec);
self.ptr = ptr;
self.len = len;
self.capacity = capacity;
}
None => {
// Case: the byte buffer is backed by a `&'static [u8]`.
if self.capacity < new_size {
panic!("cannot grow a byte buffer backed by `&'static mut [u8]` beyond its capacity")
}
let len = self.len();
self.len = new_size;
self.data_mut()[len..new_size].fill(0x00_u8);
}
Some(vec) => self.grow_vec(vec, new_size),
None => self.grow_static(new_size),
}
}

/// Grow the byte buffer to the given `new_size` when backed by a [`Vec`].
fn grow_vec(&mut self, mut vec: Vec<u8>, new_size: usize) -> Result<(), MemoryError> {
debug_assert!(vec.len() <= new_size);
let additional = new_size - vec.len();
if vec.try_reserve(additional).is_err() {
return Err(MemoryError::OutOfBoundsAllocation);
};
vec.resize(new_size, 0x00_u8);
(self.ptr, self.len, self.capacity) = vec_into_raw_parts(vec);
Ok(())
}

/// Grow the byte buffer to the given `new_size` when backed by a `&'static [u8]`.
fn grow_static(&mut self, new_size: usize) -> Result<(), MemoryError> {
if self.capacity < new_size {
return Err(MemoryError::InvalidStaticBufferSize);
}
let len = self.len();
self.len = new_size;
self.data_mut()[len..new_size].fill(0x00_u8);
Ok(())
}

/// Returns the length of the byte buffer in bytes.
Expand Down Expand Up @@ -185,14 +205,14 @@ mod test {
use super::*;
#[test]
fn test_basic_allocation_deallocation() {
let buffer = ByteBuffer::new(10);
let buffer = ByteBuffer::new(10).unwrap();
assert_eq!(buffer.len(), 10);
// Dropping the buffer should not cause UB.
}

#[test]
fn test_basic_data_manipulation() {
let mut buffer = ByteBuffer::new(10);
let mut buffer = ByteBuffer::new(10).unwrap();
assert_eq!(buffer.len(), 10);
let data = buffer.data(); // test we can read the data
assert_eq!(data, &[0; 10]);
Expand All @@ -207,7 +227,7 @@ mod test {
fn test_static_buffer_initialization() {
static mut BUF: [u8; 10] = [7; 10];
let buf = unsafe { &mut *core::ptr::addr_of_mut!(BUF) };
let mut buffer = ByteBuffer::new_static(buf, 5);
let mut buffer = ByteBuffer::new_static(buf, 5).unwrap();
assert_eq!(buffer.len(), 5);
// Modifying the static buffer through ByteBuffer and checking its content.
let data = buffer.data_mut();
Expand All @@ -219,8 +239,8 @@ mod test {

#[test]
fn test_growing_buffer() {
let mut buffer = ByteBuffer::new(5);
buffer.grow(10);
let mut buffer = ByteBuffer::new(5).unwrap();
buffer.grow(10).unwrap();
assert_eq!(buffer.len(), 10);
assert_eq!(buffer.data(), &[0; 10]);
}
Expand All @@ -229,23 +249,22 @@ mod test {
fn test_growing_static() {
static mut BUF: [u8; 10] = [7; 10];
let buf = unsafe { &mut *core::ptr::addr_of_mut!(BUF) };
let mut buffer = ByteBuffer::new_static(buf, 5);
let mut buffer = ByteBuffer::new_static(buf, 5).unwrap();
assert_eq!(buffer.len(), 5);
assert_eq!(buffer.data(), &[0; 5]);
buffer.grow(8);
buffer.grow(8).unwrap();
assert_eq!(buffer.len(), 8);
assert_eq!(buffer.data(), &[0; 8]);
buffer.grow(10);
buffer.grow(10).unwrap();
assert_eq!(buffer.len(), 10);
assert_eq!(buffer.data(), &[0; 10]);
}

#[test]
#[should_panic]
fn test_static_buffer_overflow() {
static mut BUF: [u8; 5] = [7; 5];
let buf = unsafe { &mut *core::ptr::addr_of_mut!(BUF) };
let mut buffer = ByteBuffer::new_static(buf, 5);
buffer.grow(10); // This should panic.
let mut buffer = ByteBuffer::new_static(buf, 5).unwrap();
assert!(buffer.grow(10).is_err());
}
}
10 changes: 9 additions & 1 deletion crates/wasmi/src/memory/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub enum MemoryError {
TooManyMemories,
/// Tried to create memory with invalid static buffer size
InvalidStaticBufferSize,
/// If a resource limiter denied allocation or growth of a linear memory.
ResourceLimiterDeniedAllocation,
}

#[cfg(feature = "std")]
Expand All @@ -42,7 +44,7 @@ impl Display for MemoryError {
write!(f, "out of bounds memory access")
}
Self::InvalidMemoryType => {
write!(f, "tried to create an invalid virtual memory type")
write!(f, "tried to create an invalid linear memory type")
}
Self::InvalidSubtype { ty, other } => {
write!(f, "memory type {ty:?} is not a subtype of {other:?}",)
Expand All @@ -53,6 +55,12 @@ impl Display for MemoryError {
Self::InvalidStaticBufferSize => {
write!(f, "tried to use too small static buffer")
}
Self::ResourceLimiterDeniedAllocation => {
write!(
f,
"a resource limiter denied to allocate or grow the linear memory"
)
}
}
}
}
Loading

0 comments on commit 9447890

Please sign in to comment.