Initial implementation

This commit is contained in:
Sergey Pepyakin 2018-02-08 14:06:12 +03:00
parent 528f468ef2
commit 02cf718d70
9 changed files with 125 additions and 84 deletions

View File

@ -11,6 +11,7 @@ exclude = [ "res/*", "spec/*" ]
[dependencies]
parity-wasm = "0.23"
byteorder = "1.0"
memory_units = "0.2"
[dev-dependencies]
wabt = "0.1.2"

View File

@ -16,6 +16,7 @@ use wasmi::{
Module, Signature, MemoryDescriptor, Trap,
TableDescriptor, GlobalDescriptor, FuncInstance, RuntimeArgs,
};
use wasmi::memory_units::Pages;
#[derive(Debug)]
enum Error {
@ -43,7 +44,7 @@ impl SpecModule {
fn new() -> Self {
SpecModule {
table: TableInstance::alloc(10, Some(20)).unwrap(),
memory: MemoryInstance::alloc(1, Some(2)).unwrap(),
memory: MemoryInstance::alloc(Pages(1), Some(Pages(2))).unwrap(),
global_i32: GlobalInstance::alloc(RuntimeValue::I32(666), false),
global_i64: GlobalInstance::alloc(RuntimeValue::I64(666), false),
global_f32: GlobalInstance::alloc(RuntimeValue::F32(666.0), false),

View File

@ -100,6 +100,7 @@
extern crate wabt;
extern crate parity_wasm;
extern crate byteorder;
extern crate memory_units as memory_units_crate;
use std::fmt;
use std::error;
@ -127,9 +128,9 @@ impl Trap {
}
/// Error type which can thrown by wasm code or by host environment.
///
///
/// See [`Trap`] for details.
///
///
/// [`Trap`]: struct.Trap.html
#[derive(Debug)]
pub enum TrapKind {
@ -231,9 +232,9 @@ pub enum Error {
impl Error {
/// Returns [`HostError`] if this `Error` represents some host error.
///
///
/// I.e. if this error have variant [`Host`] or [`Trap`][`Trap`] with [host][`TrapKind::Host`] error.
///
///
/// [`HostError`]: trait.HostError.html
/// [`Host`]: enum.Error.html#variant.Host
/// [`Trap`]: enum.Error.html#variant.Trap
@ -354,6 +355,12 @@ pub use self::global::{GlobalInstance, GlobalRef};
pub use self::func::{FuncInstance, FuncRef};
pub use self::types::{Signature, ValueType, GlobalDescriptor, TableDescriptor, MemoryDescriptor};
/// WebAssembly-specific sizes and units.
pub mod memory_units {
pub use memory_units_crate::wasm32::*;
pub use memory_units_crate::{Bytes, ByteSize, RoundUpTo, size_of};
}
/// Deserialized module prepared for instantiation.
pub struct Module {
labels: HashMap<usize, HashMap<usize, usize>>,

View File

@ -6,17 +6,17 @@ use std::rc::Rc;
use std::cell::RefCell;
use parity_wasm::elements::ResizableLimits;
use Error;
use module::check_limits;
use memory_units::{RoundUpTo, Pages, Bytes};
/// Size of a page of [linear memory][`MemoryInstance`] - 64KiB.
///
/// The size of a memory is always a integer multiple of a page size.
///
/// [`MemoryInstance`]: struct.MemoryInstance.html
pub const LINEAR_MEMORY_PAGE_SIZE: u32 = 65536;
pub const LINEAR_MEMORY_PAGE_SIZE: Bytes = Bytes(65536);
/// Maximal number of pages.
const LINEAR_MEMORY_MAX_PAGES: u32 = 65536;
const LINEAR_MEMORY_MAX_PAGES: Pages = Pages(65536);
/// Reference to a memory (See [`MemoryInstance`] for details).
///
@ -52,8 +52,8 @@ pub struct MemoryInstance {
limits: ResizableLimits,
/// Linear memory buffer.
buffer: RefCell<Vec<u8>>,
/// Maximum buffer size.
maximum_size: u32,
initial: Pages,
maximum: Option<Pages>,
}
impl fmt::Debug for MemoryInstance {
@ -61,7 +61,8 @@ impl fmt::Debug for MemoryInstance {
f.debug_struct("MemoryInstance")
.field("limits", &self.limits)
.field("buffer.len", &self.buffer.borrow().len())
.field("maximum_size", &self.maximum_size)
.field("maximum", &self.maximum)
.field("initial", &self.initial)
.finish()
}
}
@ -109,35 +110,26 @@ impl MemoryInstance {
/// - either `initial_pages` or `maximum_pages` is greater than `65536`.
///
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
pub fn alloc(initial_pages: u32, maximum_pages: Option<u32>) -> Result<MemoryRef, Error> {
let memory = MemoryInstance::new(ResizableLimits::new(initial_pages, maximum_pages))?;
pub fn alloc(initial: Pages, maximum: Option<Pages>) -> Result<MemoryRef, Error> {
validate_memory(initial, maximum).map_err(Error::Memory)?;
let memory = MemoryInstance::new(initial, maximum);
Ok(MemoryRef(Rc::new(memory)))
}
/// Create new linear memory instance.
fn new(limits: ResizableLimits) -> Result<Self, Error> {
check_limits(&limits)?;
let initial_pages = limits.initial();
let maximum_pages = limits.maximum().unwrap_or(LINEAR_MEMORY_MAX_PAGES);
if initial_pages > LINEAR_MEMORY_MAX_PAGES {
return Err(Error::Memory(format!("initial memory size must be at most {} pages", LINEAR_MEMORY_MAX_PAGES)));
}
if maximum_pages > LINEAR_MEMORY_MAX_PAGES {
return Err(Error::Memory(format!("maximum memory size must be at most {} pages", LINEAR_MEMORY_MAX_PAGES)));
}
let maximum_size = maximum_pages.saturating_mul(LINEAR_MEMORY_PAGE_SIZE);
let initial_size = initial_pages.saturating_mul(LINEAR_MEMORY_PAGE_SIZE);
fn new(initial: Pages, maximum: Option<Pages>) -> Self {
let limits = ResizableLimits::new(initial.0 as u32, maximum.map(|p| p.0 as u32));
let memory = MemoryInstance {
limits: limits,
buffer: RefCell::new(vec![0; initial_size as usize]),
maximum_size: maximum_size,
buffer: RefCell::new(vec![]),
initial: initial,
maximum: maximum,
};
Ok(memory)
memory.grow(initial).expect("Initial grow should always succeed");
memory
}
/// Return linear memory limits.
@ -146,21 +138,23 @@ impl MemoryInstance {
}
/// Returns number of pages this `MemoryInstance` was created with.
pub fn initial_pages(&self) -> u32 {
self.limits.initial()
pub fn initial(&self) -> Pages {
self.initial
}
/// Returns maximum amount of pages this `MemoryInstance` can grow to.
///
/// Returns `None` if there is no limit set.
/// This means that memory can grow up to 4GiB.
pub fn maximum_pages(&self) -> Option<u32> {
self.limits.maximum()
/// Maximum memory size cannot exceed `65536` pages or 4GiB.
pub fn maximum(&self) -> Option<Pages> {
self.maximum
}
/// Return linear memory size (in pages).
pub fn size(&self) -> u32 {
self.buffer.borrow().len() as u32 / LINEAR_MEMORY_PAGE_SIZE
/// Returns current linear memory size.
///
/// Maximum memory size cannot exceed `65536` pages or 4GiB.
pub fn current_size(&self) -> Pages {
Bytes(self.buffer.borrow().len()).round_up_to()
}
/// Copy data from memory at given offset.
@ -201,22 +195,37 @@ impl MemoryInstance {
/// # Errors
///
/// Returns `Err` if attempted to allocate more memory than permited by the limit.
pub fn grow(&self, pages: u32) -> Result<u32, Error> {
let mut buffer = self.buffer.borrow_mut();
let old_size = buffer.len() as u32;
match calculate_memory_size(old_size, pages, self.maximum_size) {
None => Err(Error::Memory(
format!(
"Trying to grow memory by {} pages when already have {}",
pages,
old_size / LINEAR_MEMORY_PAGE_SIZE,
)
)),
Some(new_size) => {
buffer.resize(new_size as usize, 0);
Ok(old_size / LINEAR_MEMORY_PAGE_SIZE)
},
pub fn grow(&self, additional: Pages) -> Result<Pages, Error> {
let size_before_grow: Pages = self.current_size();
println!("grow({:?}) = {:?}", additional, size_before_grow);
if additional == Pages(0) {
return Ok(size_before_grow);
}
if additional > Pages(65536) {
return Err(Error::Memory(format!(
"Trying to grow memory by more than 65536 pages"
)));
}
let new_size: Pages = size_before_grow + additional;
let maximum = self.maximum.unwrap_or(LINEAR_MEMORY_MAX_PAGES);
if new_size > maximum {
return Err(Error::Memory(format!(
"Trying to grow memory by {} pages when already have {}",
additional.0, size_before_grow.0,
)));
}
// Resize underlying buffer up to a new size filling newly allocated space with zeroes.
// This size is guaranteed to be larger than current size.
let new_buffer_length: Bytes = new_size.into();
{
let mut buffer = self.buffer.borrow_mut();
debug_assert!(new_buffer_length.0 > buffer.len());
buffer.resize(new_buffer_length.0, 0);
}
Ok(size_before_grow)
}
fn checked_region<'a, B>(&self, buffer: &'a B, offset: usize, size: usize) -> Result<CheckedRegion<'a, B>, Error>
@ -313,13 +322,24 @@ impl MemoryInstance {
}
}
fn calculate_memory_size(old_size: u32, additional_pages: u32, maximum_size: u32) -> Option<u32> {
let size = additional_pages
.saturating_mul(LINEAR_MEMORY_PAGE_SIZE);
match size.checked_add(old_size) {
Some(size) if size <= maximum_size => Some(size),
_ => None,
pub fn validate_memory(initial: Pages, maximum: Option<Pages>) -> Result<(), String> {
if initial > LINEAR_MEMORY_MAX_PAGES {
return Err(format!("initial memory size must be at most {} pages", LINEAR_MEMORY_MAX_PAGES.0));
}
if let Some(maximum) = maximum {
if initial > maximum {
return Err(format!(
"maximum limit {} is less than minimum {}",
maximum.0,
initial.0,
));
}
if maximum > LINEAR_MEMORY_MAX_PAGES {
return Err(format!("maximum memory size must be at most {} pages", LINEAR_MEMORY_MAX_PAGES.0));
}
}
Ok(())
}
#[cfg(test)]
@ -328,6 +348,7 @@ mod tests {
use super::{MemoryInstance, LINEAR_MEMORY_MAX_PAGES};
use Error;
use parity_wasm::elements::ResizableLimits;
use memory_units::Pages;
#[test]
fn alloc() {
@ -338,16 +359,19 @@ mod tests {
(1, Some(1), true),
(0, Some(1), true),
(1, Some(0), false),
(0, Some(LINEAR_MEMORY_MAX_PAGES), true),
(LINEAR_MEMORY_MAX_PAGES, Some(LINEAR_MEMORY_MAX_PAGES), true),
(LINEAR_MEMORY_MAX_PAGES, Some(0), false),
(LINEAR_MEMORY_MAX_PAGES, None, true),
(0, Some(65536), true),
(65536, Some(65536), true),
(65536, Some(0), false),
(65536, None, true),
];
for &(initial, maybe_max, expected_ok) in fixtures {
let result = MemoryInstance::alloc(initial, maybe_max);
for (index, &(initial, maybe_max, expected_ok)) in fixtures.iter().enumerate() {
let initial: Pages = Pages(initial);
let maximum: Option<Pages> = maybe_max.map(|m| Pages(m));
let result = MemoryInstance::alloc(initial, maximum);
if result.is_ok() != expected_ok {
panic!(
"unexpected error, initial={}, max={:?}, expected={}, result={:?}",
"unexpected error at {}, initial={:?}, max={:?}, expected={}, result={:?}",
index,
initial,
maybe_max,
expected_ok,
@ -358,8 +382,7 @@ mod tests {
}
fn create_memory(initial_content: &[u8]) -> MemoryInstance {
let mem = MemoryInstance::new(ResizableLimits::new(1, Some(1)))
.expect("MemoryInstance created successfuly");
let mem = MemoryInstance::new(Pages(1), Some(Pages(1)));
mem.set(0, initial_content).expect("Successful initialize the memory");
mem
}
@ -418,7 +441,7 @@ mod tests {
#[test]
fn get_into() {
let mem = MemoryInstance::new(ResizableLimits::new(1, None)).expect("memory instance creation should not fail");
let mem = MemoryInstance::new(Pages(1), None);
mem.set(6, &[13, 17, 129]).expect("memory set should not fail");
let mut data = [0u8; 2];

View File

@ -14,6 +14,7 @@ use memory::MemoryRef;
use host::Externals;
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
use types::{GlobalDescriptor, TableDescriptor, MemoryDescriptor};
use memory_units::Pages;
/// Reference to a [`ModuleInstance`].
///
@ -325,10 +326,11 @@ impl ModuleInstance {
&[],
)
{
let memory = MemoryInstance::alloc(
memory_type.limits().initial(),
memory_type.limits().maximum()
)?;
let initial: Pages = Pages(memory_type.limits().initial() as usize);
let maximum: Option<Pages> = memory_type.limits().maximum().map(|m| Pages(m as usize));
let memory = MemoryInstance::alloc(initial, maximum)
.expect("Due to validation `initial` and `maximum` should be valid");
instance.push_memory(memory);
}

View File

@ -16,6 +16,7 @@ use host::Externals;
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX, BlockFrame, BlockFrameType};
use common::stack::StackWithLimit;
use common::{DEFAULT_FRAME_STACK_LIMIT, DEFAULT_VALUE_STACK_LIMIT};
use memory_units::Pages;
/// Function interpreter.
pub struct Interpreter<'a, E: Externals + 'a> {
@ -643,7 +644,7 @@ impl<'a, E: Externals> Interpreter<'a, E> {
let m = context.module()
.memory_by_index(DEFAULT_MEMORY_INDEX)
.expect("Due to validation memory should exists");
let s = m.size();
let s = m.current_size().0;
context
.value_stack_mut()
.push(RuntimeValue::I32(s as i32))?;
@ -657,9 +658,10 @@ impl<'a, E: Externals> Interpreter<'a, E> {
let m = context.module()
.memory_by_index(DEFAULT_MEMORY_INDEX)
.expect("Due to validation memory should exists");
// Pushes -1 if allocation fails or previous memory size, if succeeds.
let m = m.grow(pages)
.unwrap_or(u32::MAX);
let m = match m.grow(Pages(pages as usize)) {
Ok(Pages(new_size)) => new_size as u32,
Err(_) => u32::MAX, // Returns -1 (or 0xFFFFFFFF) in case of error.
};
context
.value_stack_mut()
.push(RuntimeValue::I32(m as i32))?;

View File

@ -4,6 +4,7 @@ use {
RuntimeValue, RuntimeArgs, TableDescriptor, MemoryDescriptor, Trap, TrapKind,
};
use types::ValueType;
use memory_units::Pages;
use super::parse_wat;
#[derive(Debug, Clone, PartialEq)]
@ -36,7 +37,7 @@ struct TestHost {
impl TestHost {
fn new() -> TestHost {
TestHost {
memory: Some(MemoryInstance::alloc(1, Some(1)).unwrap()),
memory: Some(MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap()),
instance: None,
}
}
@ -483,7 +484,7 @@ fn defer_providing_externals() {
// Create HostImportResolver with some initialized memory instance.
// This memory instance will be provided as 'mem' export.
let host_import_resolver =
HostImportResolver { mem: MemoryInstance::alloc(1, Some(1)).unwrap() };
HostImportResolver { mem: MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap() };
// Instantiate module with `host_import_resolver` as import resolver for "host" module.
let instance = ModuleInstance::new(

View File

@ -3,6 +3,7 @@ use {
MemoryRef, ModuleImportResolver, ModuleInstance, NopExternals, RuntimeValue,
TableInstance, TableRef, Module, GlobalDescriptor, TableDescriptor, MemoryDescriptor,
};
use memory_units::Pages;
use std::fs::File;
struct Env {
@ -17,7 +18,7 @@ impl Env {
Env {
table_base: GlobalInstance::alloc(RuntimeValue::I32(0), false),
memory_base: GlobalInstance::alloc(RuntimeValue::I32(0), false),
memory: MemoryInstance::alloc(256, None).unwrap(),
memory: MemoryInstance::alloc(Pages(256), None).unwrap(),
table: TableInstance::alloc(64, None).unwrap(),
}
}

View File

@ -8,6 +8,7 @@ use parity_wasm::elements::{
use common::stack;
use self::context::ModuleContextBuilder;
use self::func::Validator;
use memory_units::Pages;
mod context;
mod func;
@ -259,7 +260,7 @@ fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> {
if let Some(maximum) = limits.maximum() {
if limits.initial() > maximum {
return Err(Error(format!(
"maximum limit {} is lesser than minimum {}",
"maximum limit {} is less than minimum {}",
maximum,
limits.initial()
)));
@ -269,7 +270,9 @@ fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> {
}
fn validate_memory_type(memory_type: &MemoryType) -> Result<(), Error> {
validate_limits(memory_type.limits())
let initial: Pages = Pages(memory_type.limits().initial() as usize);
let maximum: Option<Pages> = memory_type.limits().maximum().map(|m| Pages(m as usize));
::memory::validate_memory(initial, maximum).map_err(Error)
}
fn validate_table_type(table_type: &TableType) -> Result<(), Error> {