Memory units (#42)

* Initial implementation

* Not use grow as it is makes debug builds very slow

* Use Pages::BYTE_SIZE for LINEAR_MEMORY_PAGE_SIZE

* Tidy docs.

* Use memory_units from git.
This commit is contained in:
Sergey Pepyakin 2018-02-09 16:45:21 +03:00 committed by GitHub
parent a3ad4b0e49
commit 483736b1bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 155 additions and 90 deletions

View File

@ -11,6 +11,7 @@ exclude = [ "res/*", "spec/*" ]
[dependencies]
parity-wasm = "0.23"
byteorder = "1.0"
memory_units = { git = "https://github.com/pepyakin/memory_units.git", rev = "e09093e" }
[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;
#[cfg(all(not(feature = "32bit_opt_in"), target_pointer_width = "32"))]
compile_error! {"32-bit targets are not supported at the moment.
@ -360,6 +361,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()
}
}
@ -92,12 +93,12 @@ impl<'a, B: 'a> CheckedRegion<'a, B> where B: ::std::ops::Deref<Target=Vec<u8>>
impl MemoryInstance {
/// Allocate a memory instance.
///
/// The memory allocated with initial number of pages specified by `initial_pages`.
/// Minimal possible value for `initial_pages` is 0 and maximum possible is `65536`.
/// The memory allocated with initial number of pages specified by `initial`.
/// Minimal possible value for `initial` is 0 and maximum possible is `65536`.
/// (Since maximum addressible memory is 2<sup>32</sup> = 4GiB = 65536 * [64KiB][`LINEAR_MEMORY_PAGE_SIZE`]).
///
/// It is possible to limit maximum number of pages this memory instance can have by specifying
/// `maximum_page`. If not specified, this memory instance would be able to allocate up to 4GiB.
/// `maximum`. If not specified, this memory instance would be able to allocate up to 4GiB.
///
/// Allocated memory is always zeroed.
///
@ -105,39 +106,28 @@ impl MemoryInstance {
///
/// Returns `Err` if:
///
/// - `initial_pages` is greater than `maximum_pages`
/// - either `initial_pages` or `maximum_pages` is greater than `65536`.
/// - `initial` is greater than `maximum`
/// - either `initial` or `maximum` 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)?;
fn new(initial: Pages, maximum: Option<Pages>) -> Self {
let limits = ResizableLimits::new(initial.0 as u32, maximum.map(|p| p.0 as u32));
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);
let memory = MemoryInstance {
let initial_size: Bytes = initial.into();
MemoryInstance {
limits: limits,
buffer: RefCell::new(vec![0; initial_size as usize]),
maximum_size: maximum_size,
};
Ok(memory)
buffer: RefCell::new(vec![0; initial_size.0]),
initial: initial,
maximum: maximum,
}
}
/// Return linear memory limits.
@ -146,24 +136,47 @@ 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.
///
/// # Example
///
/// To convert number of pages to number of bytes you can use the following code:
///
/// ```rust
/// use wasmi::MemoryInstance;
/// use wasmi::memory_units::*;
///
/// let memory = MemoryInstance::alloc(Pages(1), None).unwrap();
/// let byte_size: Bytes = memory.current_size().into();
/// assert_eq!(
/// byte_size,
/// Bytes(65536),
/// );
/// ```
pub fn current_size(&self) -> Pages {
Bytes(self.buffer.borrow().len()).round_up_to()
}
/// Copy data from memory at given offset.
///
/// This will allocate vector for you.
/// If you can provide a mutable slice you can use [`get_into`].
///
/// [`get_into`]: #method.get_into
pub fn get(&self, offset: u32, size: usize) -> Result<Vec<u8>, Error> {
let buffer = self.buffer.borrow();
let region = self.checked_region(&buffer, offset as usize, size)?;
@ -196,27 +209,42 @@ impl MemoryInstance {
}
/// Increases the size of the linear memory by given number of pages.
/// Returns previous memory size (in pages) if succeeds.
/// Returns previous memory size if succeeds.
///
/// # 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,21 +341,32 @@ 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)]
mod tests {
use super::{MemoryInstance, LINEAR_MEMORY_MAX_PAGES};
use super::{MemoryInstance, LINEAR_MEMORY_PAGE_SIZE};
use Error;
use parity_wasm::elements::ResizableLimits;
use memory_units::Pages;
#[test]
fn alloc() {
@ -338,16 +377,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,
@ -357,9 +399,14 @@ mod tests {
}
}
#[test]
fn ensure_page_size() {
use memory_units::ByteSize;
assert_eq!(LINEAR_MEMORY_PAGE_SIZE, Pages::byte_size());
}
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 +465,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;
@ -267,7 +268,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()
)));
@ -277,7 +278,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> {