Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhaul Pages and Bytes types #449

Merged
merged 17 commits into from
Oct 21, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ description = "Core abstractions and primitives for the wasmi WebAssembly interp
keywords = ["wasm", "webassembly", "bytecode", "interpreter"]

[dependencies]
memory_units = "0.4.0"
libm = "0.2.1"
num-traits = { version = "0.2.8", default-features = false }
downcast-rs = { version = "1.2", default-features = false }
Expand Down
7 changes: 2 additions & 5 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
mod host_error;
mod nan_preserving_float;
mod trap;
mod units;
mod untyped;
mod value;

Expand All @@ -23,15 +24,11 @@ extern crate alloc;
#[cfg(feature = "std")]
extern crate std as alloc;

/// WebAssembly-specific sizes and units.
pub mod memory_units {
pub use memory_units::{size_of, wasm32::*, ByteSize, Bytes, RoundUpTo};
}

pub use self::{
host_error::HostError,
nan_preserving_float::{F32, F64},
trap::{Trap, TrapCode},
units::{Bytes, Pages},
untyped::{DecodeUntypedSlice, EncodeUntypedSlice, UntypedError, UntypedValue},
value::{
ArithmeticOps,
Expand Down
89 changes: 89 additions & 0 deletions core/src/units.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use core::ops::Add;

/// An amount of linear memory pages.
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Pages(u32);

impl Pages {
/// The maximum amount of pages on the `wasm32` target.
pub const fn max() -> Self {
Self(65536) // 2^16
}
}

impl Pages {
/// Creates a new amount of [`Pages`] if the amount is within bounds.
pub fn new(amount: u32) -> Option<Self> {
if amount > u32::from(Self::max()) {
return None;
}
Some(Self(amount))
}

/// Adds the given amount of pages to `self`.
///
/// Returns `Some` if the result is within bounds and `None` otherwise.
pub fn checked_add<T>(self, rhs: T) -> Option<Self>
where
T: Into<u32>,
{
let lhs = u32::from(self);
let rhs = rhs.into();
let max = u32::from(Self::max());
lhs.checked_add(rhs)
.filter(move |&result| result <= max)
.map(Self)
}

/// Returns the amount of bytes required for the amount of [`Pages`].
pub fn to_bytes(self) -> Bytes {
Bytes::from(self)
}
}

impl From<Pages> for u32 {
fn from(pages: Pages) -> Self {
pages.0
}
}

/// An amount of bytes of a linear memory.
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Bytes(u64);

impl Bytes {
/// The bytes per page on the `wasm32` target.
pub const fn per_page() -> Self {
Self(65536) // 2^16
}
}

impl Add<Pages> for Bytes {
type Output = Self;

fn add(self, pages: Pages) -> Self::Output {
let lhs = u64::from(self);
let rhs = u64::from(pages.to_bytes());
Self(lhs + rhs)
}
}

impl From<u64> for Bytes {
fn from(bytes: u64) -> Self {
Self(bytes)
}
}

impl From<Pages> for Bytes {
fn from(pages: Pages) -> Self {
Self(u64::from(u32::from(pages)) * u64::from(Self::per_page()))
}
}

impl From<Bytes> for u64 {
fn from(bytes: Bytes) -> Self {
bytes.0
}
}
6 changes: 3 additions & 3 deletions wasmi_v1/benches/benches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ fn bench_execute_memory_sum_v1(c: &mut Criterion) {
.get_export(&store, "mem")
.and_then(v1::Extern::into_memory)
.unwrap();
mem.grow(&mut store, v1::core::memory_units::Pages(1))
mem.grow(&mut store, v1::core::Pages::new(1).unwrap())
.unwrap();
let len = 100_000;
let mut expected_sum: i64 = 0;
Expand Down Expand Up @@ -549,7 +549,7 @@ fn bench_execute_memory_fill_v1(c: &mut Criterion) {
.get_export(&store, "mem")
.and_then(v1::Extern::into_memory)
.unwrap();
mem.grow(&mut store, v1::core::memory_units::Pages(1))
mem.grow(&mut store, v1::core::Pages::new(1).unwrap())
.unwrap();
let ptr = 0x100;
let len = 100_000;
Expand Down Expand Up @@ -652,7 +652,7 @@ fn bench_execute_vec_add_v1(c: &mut Criterion) {
.get_export(&store, "mem")
.and_then(v1::Extern::into_memory)
.unwrap();
mem.grow(&mut store, v1::core::memory_units::Pages(25))
mem.grow(&mut store, v1::core::Pages::new(25).unwrap())
.unwrap();
let len = 100_000;
test_for(
Expand Down
24 changes: 12 additions & 12 deletions wasmi_v1/src/engine/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
StoreContextMut,
};
use core::cmp;
use wasmi_core::{memory_units::Pages, ExtendInto, LittleEndianConvert, UntypedValue, WrapInto};
use wasmi_core::{ExtendInto, LittleEndianConvert, Pages, UntypedValue, WrapInto};

/// Executes the given function `frame`.
///
Expand Down Expand Up @@ -653,27 +653,27 @@ impl<'ctx, 'engine, 'func, HostData> Executor<'ctx, 'engine, 'func, HostData> {

fn visit_current_memory(&mut self) {
let memory = self.default_memory();
let result = memory.current_pages(self.ctx.as_context()).0 as u32;
let result: u32 = memory.current_pages(self.ctx.as_context()).into();
self.value_stack.push(result);
self.next_instr()
}

fn visit_grow_memory(&mut self) {
let pages: u32 = self.value_stack.pop_as();
/// The WebAssembly spec demands to return `0xFFFF_FFFF`
/// in case of failure for the `memory.grow` instruction.
const ERR_VALUE: u32 = u32::MAX;
let memory = self.default_memory();
let new_size = match memory.grow(self.ctx.as_context_mut(), Pages(pages as usize)) {
Ok(Pages(old_size)) => old_size as u32,
Err(_) => {
// Note: The WebAssembly spec demands to return `0xFFFF_FFFF`
// in case of failure for this instruction.
u32::MAX
}
};
let result = Pages::new(self.value_stack.pop_as()).map_or(ERR_VALUE, |additional| {
memory
.grow(self.ctx.as_context_mut(), additional)
.map(u32::from)
.unwrap_or(ERR_VALUE)
});
// The memory grow might have invalidated the cached linear memory
// so we need to reset it in order for the cache to reload in case it
// is used again.
self.cache.reset_default_memory_bytes();
self.value_stack.push(new_size);
self.value_stack.push(result);
self.next_instr()
}

Expand Down
50 changes: 40 additions & 10 deletions wasmi_v1/src/memory/byte_buffer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::{max_memory_len, MemoryError};
use super::MemoryError;
use alloc::{vec, vec::Vec};
use wasmi_core::Bytes;

/// A `Vec`-based byte buffer implementation.
///
Expand All @@ -14,30 +15,59 @@ pub struct ByteBuffer {
}

impl ByteBuffer {
#[cfg(target_pointer_width = "16")]
const fn max_len() -> u64 {
compile_error!("16-bit architectures are not supported by wasmi")
}

#[cfg(target_pointer_width = "32")]
const fn max_len() -> u64 {
i32::MAX as u32 as u64 + 1
}

#[cfg(target_pointer_width = "64")]
const fn max_len() -> u64 {
u32::MAX as u64 + 1
Copy link
Contributor

@yjhmelody yjhmelody Oct 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I could not find this value in spec. Could you give me a ref?

}

fn bytes_to_buffer_len(bytes: Bytes) -> Option<usize> {
let bytes = u64::from(bytes);
if bytes <= Self::max_len() {
Some(bytes as usize)
} else {
None
}
}

fn offset_to_new_len(&self, additional: Bytes) -> Option<usize> {
let len = self.bytes.len() as u64;
let additional = u64::from(additional);
len.checked_add(additional)
.filter(|&new_len| new_len <= Self::max_len())
.map(|new_len| new_len as usize)
}

/// Creates a new byte buffer with the given initial length.
///
/// # Errors
///
/// - If the initial length is 0.
/// - If the initial length exceeds the maximum supported limit.
pub fn new(initial_len: usize) -> Result<Self, MemoryError> {
if initial_len > max_memory_len() {
return Err(MemoryError::OutOfBoundsAllocation);
}
pub fn new(initial_len: Bytes) -> Result<Self, MemoryError> {
let initial_len =
Self::bytes_to_buffer_len(initial_len).ok_or(MemoryError::OutOfBoundsAllocation)?;
let bytes = vec![0x00_u8; initial_len];
Ok(Self { bytes })
}

/// Grows the byte buffer by the given delta.
/// Grows the byte buffer by the given amount of `additional` bytes.
///
/// # Errors
///
/// If the new length of the byte buffer would exceed the maximum supported limit.
pub fn grow(&mut self, delta: usize) -> Result<(), MemoryError> {
pub fn grow(&mut self, additional: Bytes) -> Result<(), MemoryError> {
let new_len = self
.len()
.checked_add(delta)
.filter(|&new_len| new_len < max_memory_len())
.offset_to_new_len(additional)
.ok_or(MemoryError::OutOfBoundsGrowth)?;
assert!(new_len >= self.len());
self.bytes.resize(new_len, 0x00_u8);
Expand Down
58 changes: 30 additions & 28 deletions wasmi_v1/src/memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod byte_buffer;
use self::byte_buffer::ByteBuffer;
use super::{AsContext, AsContextMut, Index, StoreContext, StoreContextMut, Stored};
use core::{fmt, fmt::Display};
use wasmi_core::memory_units::{Bytes, Pages};
use wasmi_core::Pages;

/// A raw index to a linear memory entity.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
Expand Down Expand Up @@ -32,6 +32,8 @@ pub enum MemoryError {
OutOfBoundsGrowth,
/// Tried to access linear memory out of bounds.
OutOfBoundsAccess,
/// Tried to create an invalid linear memory type.
InvalidMemoryType,
/// Occurs when a memory type does not satisfy the constraints of another.
UnsatisfyingMemoryType {
/// The unsatisfying [`MemoryType`].
Expand All @@ -53,6 +55,9 @@ impl Display for MemoryError {
Self::OutOfBoundsAccess => {
write!(f, "tried to access virtual memory out of bounds")
}
Self::InvalidMemoryType => {
write!(f, "tried to create an invalid virtual memory type")
}
Self::UnsatisfyingMemoryType {
unsatisfying,
required,
Expand All @@ -67,11 +72,6 @@ impl Display for MemoryError {
}
}

/// Returns the maximum virtual memory buffer length in bytes.
fn max_memory_len() -> usize {
i32::MAX as u32 as usize
}

/// The memory type of a linear memory.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct MemoryType {
Expand All @@ -81,11 +81,23 @@ pub struct MemoryType {

impl MemoryType {
/// Creates a new memory type with initial and optional maximum pages.
pub fn new(initial: u32, maximum: Option<u32>) -> Self {
Self {
initial_pages: Pages(initial as usize),
maximum_pages: maximum.map(|value| Pages(value as usize)),
}
///
/// # Errors
///
/// If the linear memory type initial or maximum size exceeds the
/// maximum limits of 2^16 pages.
pub fn new(initial: u32, maximum: Option<u32>) -> Result<Self, MemoryError> {
let initial_pages = Pages::new(initial).ok_or(MemoryError::InvalidMemoryType)?;
let maximum_pages = match maximum {
Some(maximum) => Pages::new(maximum)
.ok_or(MemoryError::InvalidMemoryType)?
.into(),
None => None,
};
Ok(Self {
initial_pages,
maximum_pages,
})
}

/// Returns the initial pages of the memory type.
Expand Down Expand Up @@ -139,20 +151,12 @@ pub struct MemoryEntity {
}

impl MemoryEntity {
/// The maximum amount of pages of a linear memory.
///
/// # Note
///
/// On a 32-bit platform with a page size of 65536 bytes there
/// can only be 65536 pages for a total of ~4GB bytes of memory.
const MAX_PAGES: Pages = Pages(65536);

/// Creates a new memory entity with the given memory type.
pub fn new(memory_type: MemoryType) -> Result<Self, MemoryError> {
let initial_pages = memory_type.initial_pages();
let initial_bytes = Bytes::from(initial_pages);
let initial_bytes = initial_pages.to_bytes();
let memory = Self {
bytes: ByteBuffer::new(initial_bytes.0)?,
bytes: ByteBuffer::new(initial_bytes)?,
memory_type,
current_pages: initial_pages,
};
Expand All @@ -179,23 +183,21 @@ impl MemoryEntity {
/// the grow operation.
pub fn grow(&mut self, additional: Pages) -> Result<Pages, MemoryError> {
let current_pages = self.current_pages();
if additional == Pages(0) {
if additional == Pages::default() {
// Nothing to do in this case. Bail out early.
return Ok(current_pages);
}
let maximum_pages = self
.memory_type()
.maximum_pages()
.unwrap_or(Self::MAX_PAGES);
.unwrap_or_else(Pages::max);
let new_pages = current_pages
.0
.checked_add(additional.0)
.filter(|&new_pages| new_pages <= maximum_pages.0)
.map(Pages)
.checked_add(additional)
.filter(|&new_pages| new_pages <= maximum_pages)
.ok_or(MemoryError::OutOfBoundsGrowth)?;
// At this point it is okay to grow the underlying virtual memory
// by the given amount of additional pages.
self.bytes.grow(Bytes::from(additional).0)?;
self.bytes.grow(additional.to_bytes())?;
self.current_pages = new_pages;
Ok(current_pages)
}
Expand Down
Loading