Implement the memory64 proposal in Wasmtime (#3153)
* Implement the memory64 proposal in Wasmtime This commit implements the WebAssembly [memory64 proposal][proposal] in both Wasmtime and Cranelift. In terms of work done Cranelift ended up needing very little work here since most of it was already prepared for 64-bit memories at one point or another. Most of the work in Wasmtime is largely refactoring, changing a bunch of `u32` values to something else. A number of internal and public interfaces are changing as a result of this commit, for example: * Acessors on `wasmtime::Memory` that work with pages now all return `u64` unconditionally rather than `u32`. This makes it possible to accommodate 64-bit memories with this API, but we may also want to consider `usize` here at some point since the host can't grow past `usize`-limited pages anyway. * The `wasmtime::Limits` structure is removed in favor of minimum/maximum methods on table/memory types. * Many libcall intrinsics called by jit code now unconditionally take `u64` arguments instead of `u32`. Return values are `usize`, however, since the return value, if successful, is always bounded by host memory while arguments can come from any guest. * The `heap_addr` clif instruction now takes a 64-bit offset argument instead of a 32-bit one. It turns out that the legalization of `heap_addr` already worked with 64-bit offsets, so this change was fairly trivial to make. * The runtime implementation of mmap-based linear memories has changed to largely work in `usize` quantities in its API and in bytes instead of pages. This simplifies various aspects and reflects that mmap-memories are always bound by `usize` since that's what the host is using to address things, and additionally most calculations care about bytes rather than pages except for the very edge where we're going to/from wasm. Overall I've tried to minimize the amount of `as` casts as possible, using checked `try_from` and checked arithemtic with either error handling or explicit `unwrap()` calls to tell us about bugs in the future. Most locations have relatively obvious things to do with various implications on various hosts, and I think they should all be roughly of the right shape but time will tell. I mostly relied on the compiler complaining that various types weren't aligned to figure out type-casting, and I manually audited some of the more obvious locations. I suspect we have a number of hidden locations that will panic on 32-bit hosts if 64-bit modules try to run there, but otherwise I think we should be generally ok (famous last words). In any case I wouldn't want to enable this by default naturally until we've fuzzed it for some time. In terms of the actual underlying implementation, no one should expect memory64 to be all that fast. Right now it's implemented with "dynamic" heaps which have a few consequences: * All memory accesses are bounds-checked. I'm not sure how aggressively Cranelift tries to optimize out bounds checks, but I suspect not a ton since we haven't stressed this much historically. * Heaps are always precisely sized. This means that every call to `memory.grow` will incur a `memcpy` of memory from the old heap to the new. We probably want to at least look into `mremap` on Linux and otherwise try to implement schemes where dynamic heaps have some reserved pages to grow into to help amortize the cost of `memory.grow`. The memory64 spec test suite is scheduled to now run on CI, but as with all the other spec test suites it's really not all that comprehensive. I've tried adding more tests for basic things as I've had to implement guards for them, but I wouldn't really consider the testing adequate from just this PR itself. I did try to take care in one test to actually allocate a 4gb+ heap and then avoid running that in the pooling allocator or in emulation because otherwise that may fail or take excessively long. [proposal]: https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md * Fix some tests * More test fixes * Fix wasmtime tests * Fix doctests * Revert to 32-bit immediate offsets in `heap_addr` This commit updates the generation of addresses in wasm code to always use 32-bit offsets for `heap_addr`, and if the calculated offset is bigger than 32-bits we emit a manual add with an overflow check. * Disable memory64 for spectest fuzzing * Fix wrong offset being added to heap addr * More comments! * Clarify bytes/pages
This commit is contained in:
@@ -3,7 +3,6 @@ use crate::trampoline::MemoryCreatorProxy;
|
||||
use anyhow::{bail, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
#[cfg(feature = "cache")]
|
||||
use std::path::Path;
|
||||
@@ -136,7 +135,7 @@ pub struct ModuleLimits {
|
||||
/// The reservation size of each linear memory is controlled by the
|
||||
/// [`static_memory_maximum_size`](Config::static_memory_maximum_size) setting and this value cannot
|
||||
/// exceed the configured static memory maximum size.
|
||||
pub memory_pages: u32,
|
||||
pub memory_pages: u64,
|
||||
}
|
||||
|
||||
impl Default for ModuleLimits {
|
||||
@@ -773,6 +772,21 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures whether the WebAssembly memory64 [proposal] will
|
||||
/// be enabled for compilation.
|
||||
///
|
||||
/// Note that this the upstream specification is not finalized and Wasmtime
|
||||
/// may also have bugs for this feature since it hasn't been exercised
|
||||
/// much.
|
||||
///
|
||||
/// This is `false` by default.
|
||||
///
|
||||
/// [proposal]: https://github.com/webassembly/memory64
|
||||
pub fn wasm_memory64(&mut self, enable: bool) -> &mut Self {
|
||||
self.features.memory64 = enable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures which compilation strategy will be used for wasm modules.
|
||||
///
|
||||
/// This method can be used to configure which compiler is used for wasm
|
||||
@@ -1081,7 +1095,7 @@ impl Config {
|
||||
/// pooling allocator.
|
||||
pub fn static_memory_maximum_size(&mut self, max_size: u64) -> &mut Self {
|
||||
let max_pages = max_size / u64::from(wasmtime_environ::WASM_PAGE_SIZE);
|
||||
self.tunables.static_memory_bound = u32::try_from(max_pages).unwrap_or(u32::max_value());
|
||||
self.tunables.static_memory_bound = max_pages;
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -419,7 +419,7 @@ impl Table {
|
||||
/// let engine = Engine::default();
|
||||
/// let mut store = Store::new(&engine, ());
|
||||
///
|
||||
/// let ty = TableType::new(ValType::FuncRef, Limits::new(2, None));
|
||||
/// let ty = TableType::new(ValType::FuncRef, 2, None);
|
||||
/// let table = Table::new(&mut store, ty, Val::FuncRef(None))?;
|
||||
///
|
||||
/// let module = Module::new(
|
||||
@@ -442,7 +442,7 @@ impl Table {
|
||||
}
|
||||
|
||||
fn _new(store: &mut StoreOpaque, ty: TableType, init: Val) -> Result<Table> {
|
||||
if init.ty() != *ty.element() {
|
||||
if init.ty() != ty.element() {
|
||||
bail!(
|
||||
"table initialization value type {:?} does not have expected type {:?}",
|
||||
init.ty(),
|
||||
@@ -467,7 +467,7 @@ impl Table {
|
||||
unsafe {
|
||||
let table = Table::from_wasmtime_table(wasmtime_export, store);
|
||||
(*table.wasmtime_table(store))
|
||||
.fill(0, init, ty.limits().min())
|
||||
.fill(0, init, ty.minimum())
|
||||
.map_err(Trap::from_runtime)?;
|
||||
|
||||
Ok(table)
|
||||
|
||||
@@ -9,13 +9,13 @@ impl StoreLimitsBuilder {
|
||||
Self(StoreLimits::default())
|
||||
}
|
||||
|
||||
/// The maximum number of WebAssembly pages a linear memory can grow to.
|
||||
/// The maximum number of bytes a linear memory can grow to.
|
||||
///
|
||||
/// Growing a linear memory beyond this limit will fail.
|
||||
///
|
||||
/// By default, linear memory pages will not be limited.
|
||||
pub fn memory_pages(mut self, limit: u32) -> Self {
|
||||
self.0.memory_pages = Some(limit);
|
||||
/// By default, linear memory will not be limited.
|
||||
pub fn memory_size(mut self, limit: usize) -> Self {
|
||||
self.0.memory_size = Some(limit);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ impl StoreLimitsBuilder {
|
||||
|
||||
/// Provides limits for a [`Store`](crate::Store).
|
||||
pub struct StoreLimits {
|
||||
memory_pages: Option<u32>,
|
||||
memory_size: Option<usize>,
|
||||
table_elements: Option<u32>,
|
||||
instances: usize,
|
||||
tables: usize,
|
||||
@@ -77,7 +77,7 @@ pub struct StoreLimits {
|
||||
impl Default for StoreLimits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
memory_pages: None,
|
||||
memory_size: None,
|
||||
table_elements: None,
|
||||
instances: wasmtime_runtime::DEFAULT_INSTANCE_LIMIT,
|
||||
tables: wasmtime_runtime::DEFAULT_TABLE_LIMIT,
|
||||
@@ -87,8 +87,8 @@ impl Default for StoreLimits {
|
||||
}
|
||||
|
||||
impl ResourceLimiter for StoreLimits {
|
||||
fn memory_growing(&mut self, _current: u32, desired: u32, _maximum: Option<u32>) -> bool {
|
||||
match self.memory_pages {
|
||||
fn memory_growing(&mut self, _current: usize, desired: usize, _maximum: Option<usize>) -> bool {
|
||||
match self.memory_size {
|
||||
Some(limit) if desired > limit => false,
|
||||
_ => true,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::store::{StoreData, StoreOpaque, Stored};
|
||||
use crate::trampoline::generate_memory_export;
|
||||
use crate::{AsContext, AsContextMut, MemoryType, StoreContext, StoreContextMut};
|
||||
use anyhow::{bail, Result};
|
||||
use std::convert::TryFrom;
|
||||
use std::slice;
|
||||
|
||||
/// Error for out of bounds [`Memory`] access.
|
||||
@@ -209,7 +210,7 @@ impl Memory {
|
||||
/// let engine = Engine::default();
|
||||
/// let mut store = Store::new(&engine, ());
|
||||
///
|
||||
/// let memory_ty = MemoryType::new(Limits::new(1, None));
|
||||
/// let memory_ty = MemoryType::new(1, None);
|
||||
/// let memory = Memory::new(&mut store, memory_ty)?;
|
||||
///
|
||||
/// let module = Module::new(&engine, "(module (memory (import \"\" \"\") 1))")?;
|
||||
@@ -246,7 +247,7 @@ impl Memory {
|
||||
/// let instance = Instance::new(&mut store, &module, &[])?;
|
||||
/// let memory = instance.get_memory(&mut store, "mem").unwrap();
|
||||
/// let ty = memory.ty(&store);
|
||||
/// assert_eq!(ty.limits().min(), 1);
|
||||
/// assert_eq!(ty.minimum(), 1);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
@@ -403,8 +404,8 @@ impl Memory {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if this memory doesn't belong to `store`.
|
||||
pub fn size(&self, store: impl AsContext) -> u32 {
|
||||
(self.data_size(store) / wasmtime_environ::WASM_PAGE_SIZE as usize) as u32
|
||||
pub fn size(&self, store: impl AsContext) -> u64 {
|
||||
(self.data_size(store) / wasmtime_environ::WASM_PAGE_SIZE as usize) as u64
|
||||
}
|
||||
|
||||
/// Grows this WebAssembly memory by `delta` pages.
|
||||
@@ -448,7 +449,7 @@ impl Memory {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn grow(&self, mut store: impl AsContextMut, delta: u32) -> Result<u32> {
|
||||
pub fn grow(&self, mut store: impl AsContextMut, delta: u64) -> Result<u64> {
|
||||
let mem = self.wasmtime_memory(&mut store.as_context_mut().opaque());
|
||||
let store = store.as_context_mut();
|
||||
unsafe {
|
||||
@@ -456,7 +457,7 @@ impl Memory {
|
||||
Some(size) => {
|
||||
let vm = (*mem).vmmemory();
|
||||
*store[self.0].definition = vm;
|
||||
Ok(size)
|
||||
Ok(u64::try_from(size).unwrap() / u64::from(wasmtime_environ::WASM_PAGE_SIZE))
|
||||
}
|
||||
None => bail!("failed to grow memory by `{}`", delta),
|
||||
}
|
||||
@@ -499,10 +500,11 @@ impl Memory {
|
||||
}
|
||||
}
|
||||
|
||||
/// A linear memory. This trait provides an interface for raw memory buffers which are used
|
||||
/// by wasmtime, e.g. inside ['Memory']. Such buffers are in principle not thread safe.
|
||||
/// By implementing this trait together with MemoryCreator,
|
||||
/// one can supply wasmtime with custom allocated host managed memory.
|
||||
/// A linear memory. This trait provides an interface for raw memory buffers
|
||||
/// which are used by wasmtime, e.g. inside ['Memory']. Such buffers are in
|
||||
/// principle not thread safe. By implementing this trait together with
|
||||
/// MemoryCreator, one can supply wasmtime with custom allocated host managed
|
||||
/// memory.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
@@ -514,18 +516,20 @@ impl Memory {
|
||||
/// Note that this is a relatively new and experimental feature and it is
|
||||
/// recommended to be familiar with wasmtime runtime code to use it.
|
||||
pub unsafe trait LinearMemory: Send + Sync + 'static {
|
||||
/// Returns the number of allocated wasm pages.
|
||||
fn size(&self) -> u32;
|
||||
/// Returns the number of allocated bytes which are accessible at this time.
|
||||
fn byte_size(&self) -> usize;
|
||||
|
||||
/// Returns the maximum number of pages the memory can grow to.
|
||||
/// Returns `None` if the memory is unbounded.
|
||||
fn maximum(&self) -> Option<u32>;
|
||||
/// Returns the maximum number of bytes the memory can grow to.
|
||||
///
|
||||
/// Returns `None` if the memory is unbounded, or `Some` if memory cannot
|
||||
/// grow beyond a specified limit.
|
||||
fn maximum_byte_size(&self) -> Option<usize>;
|
||||
|
||||
/// Grow memory by the specified amount of wasm pages.
|
||||
/// Grows this memory to have the `new_size`, in bytes, specified.
|
||||
///
|
||||
/// Returns `None` if memory can't be grown by the specified amount
|
||||
/// of wasm pages.
|
||||
fn grow(&mut self, delta: u32) -> Option<u32>;
|
||||
/// of bytes. Returns `Some` if memory was grown successfully.
|
||||
fn grow_to(&mut self, new_size: usize) -> Option<()>;
|
||||
|
||||
/// Return the allocated memory as a mutable pointer to u8.
|
||||
fn as_ptr(&self) -> *mut u8;
|
||||
@@ -547,7 +551,9 @@ pub unsafe trait MemoryCreator: Send + Sync {
|
||||
/// Create a new `LinearMemory` object from the specified parameters.
|
||||
///
|
||||
/// The type of memory being created is specified by `ty` which indicates
|
||||
/// both the minimum and maximum size, in wasm pages.
|
||||
/// both the minimum and maximum size, in wasm pages. The minimum and
|
||||
/// maximum sizes, in bytes, are also specified as parameters to avoid
|
||||
/// integer conversion if desired.
|
||||
///
|
||||
/// The `reserved_size_in_bytes` value indicates the expected size of the
|
||||
/// reservation that is to be made for this memory. If this value is `None`
|
||||
@@ -557,23 +563,27 @@ pub unsafe trait MemoryCreator: Send + Sync {
|
||||
/// size at the end. Note that this reservation need only be a virtual
|
||||
/// memory reservation, physical memory does not need to be allocated
|
||||
/// immediately. In this case `grow` should never move the base pointer and
|
||||
/// the maximum size of `ty` is guaranteed to fit within `reserved_size_in_bytes`.
|
||||
/// the maximum size of `ty` is guaranteed to fit within
|
||||
/// `reserved_size_in_bytes`.
|
||||
///
|
||||
/// The `guard_size_in_bytes` parameter indicates how many bytes of space, after the
|
||||
/// memory allocation, is expected to be unmapped. JIT code will elide
|
||||
/// bounds checks based on the `guard_size_in_bytes` provided, so for JIT code to
|
||||
/// work correctly the memory returned will need to be properly guarded with
|
||||
/// `guard_size_in_bytes` bytes left unmapped after the base allocation.
|
||||
/// The `guard_size_in_bytes` parameter indicates how many bytes of space,
|
||||
/// after the memory allocation, is expected to be unmapped. JIT code will
|
||||
/// elide bounds checks based on the `guard_size_in_bytes` provided, so for
|
||||
/// JIT code to work correctly the memory returned will need to be properly
|
||||
/// guarded with `guard_size_in_bytes` bytes left unmapped after the base
|
||||
/// allocation.
|
||||
///
|
||||
/// Note that the `reserved_size_in_bytes` and `guard_size_in_bytes` options are tuned from
|
||||
/// the various [`Config`](crate::Config) methods about memory
|
||||
/// sizes/guards. Additionally these two values are guaranteed to be
|
||||
/// Note that the `reserved_size_in_bytes` and `guard_size_in_bytes` options
|
||||
/// are tuned from the various [`Config`](crate::Config) methods about
|
||||
/// memory sizes/guards. Additionally these two values are guaranteed to be
|
||||
/// multiples of the system page size.
|
||||
fn new_memory(
|
||||
&self,
|
||||
ty: MemoryType,
|
||||
reserved_size_in_bytes: Option<u64>,
|
||||
guard_size_in_bytes: u64,
|
||||
minimum: usize,
|
||||
maximum: Option<usize>,
|
||||
reserved_size_in_bytes: Option<usize>,
|
||||
guard_size_in_bytes: usize,
|
||||
) -> Result<Box<dyn LinearMemory>, String>;
|
||||
}
|
||||
|
||||
@@ -589,7 +599,7 @@ mod tests {
|
||||
cfg.static_memory_maximum_size(0)
|
||||
.dynamic_memory_guard_size(0);
|
||||
let mut store = Store::new(&Engine::new(&cfg).unwrap(), ());
|
||||
let ty = MemoryType::new(Limits::new(1, None));
|
||||
let ty = MemoryType::new(1, None);
|
||||
let mem = Memory::new(&mut store, ty).unwrap();
|
||||
let store = store.as_context();
|
||||
assert_eq!(store[mem.0].memory.offset_guard_size, 0);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::memory::{LinearMemory, MemoryCreator};
|
||||
use crate::store::{InstanceId, StoreOpaque};
|
||||
use crate::trampoline::create_handle;
|
||||
use crate::{Limits, MemoryType};
|
||||
use crate::MemoryType;
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::{wasm, MemoryPlan, MemoryStyle, Module, WASM_PAGE_SIZE};
|
||||
@@ -11,14 +12,10 @@ use wasmtime_runtime::{RuntimeLinearMemory, RuntimeMemoryCreator, VMMemoryDefini
|
||||
pub fn create_memory(store: &mut StoreOpaque<'_>, memory: &MemoryType) -> Result<InstanceId> {
|
||||
let mut module = Module::new();
|
||||
|
||||
let memory = wasm::Memory {
|
||||
minimum: memory.limits().min(),
|
||||
maximum: memory.limits().max(),
|
||||
shared: false, // TODO
|
||||
};
|
||||
|
||||
let memory_plan =
|
||||
wasmtime_environ::MemoryPlan::for_memory(memory, &store.engine().config().tunables);
|
||||
let memory_plan = wasmtime_environ::MemoryPlan::for_memory(
|
||||
memory.wasmtime_memory().clone(),
|
||||
&store.engine().config().tunables,
|
||||
);
|
||||
let memory_id = module.memory_plans.push(memory_plan);
|
||||
module
|
||||
.exports
|
||||
@@ -32,22 +29,22 @@ struct LinearMemoryProxy {
|
||||
}
|
||||
|
||||
impl RuntimeLinearMemory for LinearMemoryProxy {
|
||||
fn size(&self) -> u32 {
|
||||
self.mem.size()
|
||||
fn byte_size(&self) -> usize {
|
||||
self.mem.byte_size()
|
||||
}
|
||||
|
||||
fn maximum(&self) -> Option<u32> {
|
||||
self.mem.maximum()
|
||||
fn maximum_byte_size(&self) -> Option<usize> {
|
||||
self.mem.maximum_byte_size()
|
||||
}
|
||||
|
||||
fn grow(&mut self, delta: u32) -> Option<u32> {
|
||||
self.mem.grow(delta)
|
||||
fn grow_to(&mut self, new_size: usize) -> Option<()> {
|
||||
self.mem.grow_to(new_size)
|
||||
}
|
||||
|
||||
fn vmmemory(&self) -> VMMemoryDefinition {
|
||||
VMMemoryDefinition {
|
||||
base: self.mem.as_ptr(),
|
||||
current_length: self.mem.size() as usize * WASM_PAGE_SIZE as usize,
|
||||
current_length: self.mem.byte_size(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,14 +53,27 @@ impl RuntimeLinearMemory for LinearMemoryProxy {
|
||||
pub(crate) struct MemoryCreatorProxy(pub Arc<dyn MemoryCreator>);
|
||||
|
||||
impl RuntimeMemoryCreator for MemoryCreatorProxy {
|
||||
fn new_memory(&self, plan: &MemoryPlan) -> Result<Box<dyn RuntimeLinearMemory>> {
|
||||
let ty = MemoryType::new(Limits::new(plan.memory.minimum, plan.memory.maximum));
|
||||
fn new_memory(
|
||||
&self,
|
||||
plan: &MemoryPlan,
|
||||
minimum: usize,
|
||||
maximum: Option<usize>,
|
||||
) -> Result<Box<dyn RuntimeLinearMemory>> {
|
||||
let ty = MemoryType::from_wasmtime_memory(&plan.memory);
|
||||
let reserved_size_in_bytes = match plan.style {
|
||||
MemoryStyle::Static { bound } => Some(bound as u64 * WASM_PAGE_SIZE as u64),
|
||||
MemoryStyle::Static { bound } => {
|
||||
Some(usize::try_from(bound * (WASM_PAGE_SIZE as u64)).unwrap())
|
||||
}
|
||||
MemoryStyle::Dynamic => None,
|
||||
};
|
||||
self.0
|
||||
.new_memory(ty, reserved_size_in_bytes, plan.offset_guard_size)
|
||||
.new_memory(
|
||||
ty,
|
||||
minimum,
|
||||
maximum,
|
||||
reserved_size_in_bytes,
|
||||
usize::try_from(plan.offset_guard_size).unwrap(),
|
||||
)
|
||||
.map(|mem| Box::new(LinearMemoryProxy { mem }) as Box<dyn RuntimeLinearMemory>)
|
||||
.map_err(|e| anyhow!(e))
|
||||
}
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
use crate::store::{InstanceId, StoreOpaque};
|
||||
use crate::trampoline::create_handle;
|
||||
use crate::{TableType, ValType};
|
||||
use anyhow::{bail, Result};
|
||||
use crate::TableType;
|
||||
use anyhow::Result;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::{wasm, Module};
|
||||
|
||||
pub fn create_table(store: &mut StoreOpaque<'_>, table: &TableType) -> Result<InstanceId> {
|
||||
let mut module = Module::new();
|
||||
|
||||
let table = wasm::Table {
|
||||
wasm_ty: table.element().to_wasm_type(),
|
||||
minimum: table.limits().min(),
|
||||
maximum: table.limits().max(),
|
||||
ty: match table.element() {
|
||||
ValType::FuncRef => wasm::TableElementType::Func,
|
||||
ValType::ExternRef => wasm::TableElementType::Val(wasmtime_runtime::ref_type()),
|
||||
_ => bail!("cannot support {:?} as a table element", table.element()),
|
||||
},
|
||||
};
|
||||
let tunable = Default::default();
|
||||
|
||||
let table_plan = wasmtime_environ::TablePlan::for_table(table, &tunable);
|
||||
let table_plan = wasmtime_environ::TablePlan::for_table(
|
||||
table.wasmtime_table().clone(),
|
||||
&store.engine().config().tunables,
|
||||
);
|
||||
let table_id = module.table_plans.push(table_plan);
|
||||
// TODO: can this `exports.insert` get removed?
|
||||
module
|
||||
|
||||
@@ -18,38 +18,6 @@ pub enum Mutability {
|
||||
Var,
|
||||
}
|
||||
|
||||
/// Limits of tables/memories where the units of the limits are defined by the
|
||||
/// table/memory types.
|
||||
///
|
||||
/// A minimum is always available but the maximum may not be present.
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct Limits {
|
||||
min: u32,
|
||||
max: Option<u32>,
|
||||
}
|
||||
|
||||
impl Limits {
|
||||
/// Creates a new set of limits with the minimum and maximum both specified.
|
||||
pub fn new(min: u32, max: Option<u32>) -> Limits {
|
||||
Limits { min, max }
|
||||
}
|
||||
|
||||
/// Creates a new `Limits` with the `min` specified and no maximum specified.
|
||||
pub fn at_least(min: u32) -> Limits {
|
||||
Limits::new(min, None)
|
||||
}
|
||||
|
||||
/// Returns the minimum amount for these limits.
|
||||
pub fn min(&self) -> u32 {
|
||||
self.min
|
||||
}
|
||||
|
||||
/// Returns the maximum amount for these limits, if specified.
|
||||
pub fn max(&self) -> Option<u32> {
|
||||
self.max
|
||||
}
|
||||
}
|
||||
|
||||
// Value Types
|
||||
|
||||
/// A list of all possible value types in WebAssembly.
|
||||
@@ -357,38 +325,50 @@ impl GlobalType {
|
||||
/// which `call_indirect` can invoke other functions.
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct TableType {
|
||||
element: ValType,
|
||||
limits: Limits,
|
||||
ty: wasm::Table,
|
||||
}
|
||||
|
||||
impl TableType {
|
||||
/// Creates a new table descriptor which will contain the specified
|
||||
/// `element` and have the `limits` applied to its length.
|
||||
pub fn new(element: ValType, limits: Limits) -> TableType {
|
||||
TableType { element, limits }
|
||||
pub fn new(element: ValType, min: u32, max: Option<u32>) -> TableType {
|
||||
TableType {
|
||||
ty: wasm::Table {
|
||||
ty: match element {
|
||||
ValType::FuncRef => wasm::TableElementType::Func,
|
||||
_ => wasm::TableElementType::Val(element.get_wasmtime_type()),
|
||||
},
|
||||
wasm_ty: element.to_wasm_type(),
|
||||
minimum: min,
|
||||
maximum: max,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the element value type of this table.
|
||||
pub fn element(&self) -> &ValType {
|
||||
&self.element
|
||||
pub fn element(&self) -> ValType {
|
||||
ValType::from_wasm_type(&self.ty.wasm_ty)
|
||||
}
|
||||
|
||||
/// Returns the limits, in units of elements, of this table.
|
||||
pub fn limits(&self) -> &Limits {
|
||||
&self.limits
|
||||
/// Returns minimum number of elements this table must have
|
||||
pub fn minimum(&self) -> u32 {
|
||||
self.ty.minimum
|
||||
}
|
||||
|
||||
/// Returns the optionally-specified maximum number of elements this table
|
||||
/// can have.
|
||||
///
|
||||
/// If this returns `None` then the table is not limited in size.
|
||||
pub fn maximum(&self) -> Option<u32> {
|
||||
self.ty.maximum
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_table(table: &wasm::Table) -> TableType {
|
||||
let ty = match table.ty {
|
||||
wasm::TableElementType::Func => ValType::FuncRef,
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
wasm::TableElementType::Val(ir::types::R64) => ValType::ExternRef,
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
wasm::TableElementType::Val(ir::types::R32) => ValType::ExternRef,
|
||||
_ => panic!("only `funcref` and `externref` tables supported"),
|
||||
};
|
||||
let limits = Limits::new(table.minimum, table.maximum);
|
||||
TableType::new(ty, limits)
|
||||
TableType { ty: table.clone() }
|
||||
}
|
||||
|
||||
pub(crate) fn wasmtime_table(&self) -> &wasm::Table {
|
||||
&self.ty
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,23 +380,78 @@ impl TableType {
|
||||
/// chunks of addressable memory.
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct MemoryType {
|
||||
limits: Limits,
|
||||
ty: wasm::Memory,
|
||||
}
|
||||
|
||||
impl MemoryType {
|
||||
/// Creates a new descriptor for a WebAssembly memory given the specified
|
||||
/// limits of the memory.
|
||||
pub fn new(limits: Limits) -> MemoryType {
|
||||
MemoryType { limits }
|
||||
/// Creates a new descriptor for a 32-bit WebAssembly memory given the
|
||||
/// specified limits of the memory.
|
||||
///
|
||||
/// The `minimum` and `maximum` values here are specified in units of
|
||||
/// WebAssembly pages, which are 64k.
|
||||
pub fn new(minimum: u32, maximum: Option<u32>) -> MemoryType {
|
||||
MemoryType {
|
||||
ty: wasm::Memory {
|
||||
memory64: false,
|
||||
shared: false,
|
||||
minimum: minimum.into(),
|
||||
maximum: maximum.map(|i| i.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the limits (in pages) that are configured for this memory.
|
||||
pub fn limits(&self) -> &Limits {
|
||||
&self.limits
|
||||
/// Creates a new descriptor for a 64-bit WebAssembly memory given the
|
||||
/// specified limits of the memory.
|
||||
///
|
||||
/// The `minimum` and `maximum` values here are specified in units of
|
||||
/// WebAssembly pages, which are 64k.
|
||||
///
|
||||
/// Note that 64-bit memories are part of the memory64 proposal for
|
||||
/// WebAssembly which is not standardized yet.
|
||||
pub fn new64(minimum: u64, maximum: Option<u64>) -> MemoryType {
|
||||
MemoryType {
|
||||
ty: wasm::Memory {
|
||||
memory64: true,
|
||||
shared: false,
|
||||
minimum,
|
||||
maximum,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether this is a 64-bit memory or not.
|
||||
///
|
||||
/// Note that 64-bit memories are part of the memory64 proposal for
|
||||
/// WebAssembly which is not standardized yet.
|
||||
pub fn is_64(&self) -> bool {
|
||||
self.ty.memory64
|
||||
}
|
||||
|
||||
/// Returns minimum number of WebAssembly pages this memory must have.
|
||||
///
|
||||
/// Note that the return value, while a `u64`, will always fit into a `u32`
|
||||
/// for 32-bit memories.
|
||||
pub fn minimum(&self) -> u64 {
|
||||
self.ty.minimum
|
||||
}
|
||||
|
||||
/// Returns the optionally-specified maximum number of pages this memory
|
||||
/// can have.
|
||||
///
|
||||
/// If this returns `None` then the memory is not limited in size.
|
||||
///
|
||||
/// Note that the return value, while a `u64`, will always fit into a `u32`
|
||||
/// for 32-bit memories.
|
||||
pub fn maximum(&self) -> Option<u64> {
|
||||
self.ty.maximum
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_memory(memory: &wasm::Memory) -> MemoryType {
|
||||
MemoryType::new(Limits::new(memory.minimum, memory.maximum))
|
||||
MemoryType { ty: memory.clone() }
|
||||
}
|
||||
|
||||
pub(crate) fn wasmtime_memory(&self) -> &wasm::Memory {
|
||||
&self.ty
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ impl MatchCx<'_> {
|
||||
|
||||
fn memory_ty(&self, expected: &Memory, actual: &Memory) -> Result<()> {
|
||||
if expected.shared == actual.shared
|
||||
&& expected.memory64 == actual.memory64
|
||||
&& expected.minimum <= actual.minimum
|
||||
&& match expected.maximum {
|
||||
Some(expected) => match actual.maximum {
|
||||
|
||||
Reference in New Issue
Block a user