Skip to content

Commit

Permalink
Implement bulk-memory Wasm proposal (#628)
Browse files Browse the repository at this point in the history
* add bulk-memory-operations Wasm spec tests

They are currently all failing obviously since bulk-memory has not yet been implemented.
The goal is to implement bulk-memory until all tests pass in this PR.

* add bulk_memory flag to Config

* refactor Wasm spec test suite runners

* refactor define_X_tests macros

* add comment about mutable-global

* remove ModuleError::Unsupported

Those errors are now panics which makes more sense considering the fact that the wasmparser crate already filters out all of those items unless there is a bug. And if there is a bug we should panic instead of properly managing the error.

* cleanup after last commit to use panics instead of unsupported error

* implement initial skeleton for passive data segments

* export DataSegmentKind from the data module and use it

* refactor data segments minimally

* add skeleton for passive Wasm element segments

* refactor InitExpr

* add InstanceDataSegment to InstanceEntity

* add data segments to instance and store

* implement memory.init Wasm instruction

* replace todo!() with unimplemented!()

* fix doc links

* implement DataDrop wasmi instruction

* add missing docs

* remove commented out line

* add wasmi memory.fill instruction

* implement wasmi memory.copy instruction

* remove unused APIs

* fix bug in bulk-memory instructions implemented so far

* move memory query down

* fix bug in some bulk-memory instruction impls

Now 3 of the 8 bulk-memory Wasm spec tests pass!

* adjust InitExpr and module::ElementSegment

* fix bugs

* add element segments to the store

* implement table.init and elem.drop Wasm instructions

* update wast from 0.44 to 0.45

* update wast 0.45 -> 0.46

* update wast 0.46 -> 0.52

* implement Wasm table.copy instruction

* implement user facing Table::copy API

* update Wasm testsuite revision

Many tests are failing now. Next steps are to make them pass again.

* cleanup in tests

* fix typo

* fix Wasm spectest suite test case

* remove some forwarding methods in Store

* fix remaining bugs

* fix code after merge

* rename tests

* expect failure for all reference-types related tests

* fix internal doc link
  • Loading branch information
Robbepop authored Jan 25, 2023
1 parent 77e574c commit 5f9d81a
Show file tree
Hide file tree
Showing 24 changed files with 1,399 additions and 358 deletions.
16 changes: 16 additions & 0 deletions crates/arena/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ mod tests;
pub use self::{component_vec::ComponentVec, dedup::DedupArena, guarded::GuardedEntity};
use alloc::vec::Vec;
use core::{
cmp::max,
iter::{DoubleEndedIterator, Enumerate, ExactSizeIterator},
marker::PhantomData,
ops::{Index, IndexMut},
Expand Down Expand Up @@ -149,6 +150,21 @@ where
pub fn get_mut(&mut self, index: Idx) -> Option<&mut T> {
self.entities.get_mut(index.into_usize())
}

/// Returns an exclusive reference to the pair of entities at the given indices if any.
///
/// Returns `None` if `fst` and `snd` refer to the same entity.
/// Returns `None` if either `fst` or `snd` is invalid for this [`Arena`].
#[inline]
pub fn get_pair_mut(&mut self, fst: Idx, snd: Idx) -> Option<(&mut T, &mut T)> {
let fst_index = fst.into_usize();
let snd_index = snd.into_usize();
let max_index = max(fst_index, snd_index);
let (fst_set, snd_set) = self.entities.split_at_mut(max_index);
let fst = fst_set.get_mut(fst_index)?;
let snd = snd_set.get_mut(0)?;
Some((fst, snd))
}
}

impl<Idx, T> FromIterator<T> for Arena<Idx, T> {
Expand Down
100 changes: 100 additions & 0 deletions crates/wasmi/src/element.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use crate::{module, module::FuncIdx, store::Stored, AsContextMut};
use alloc::sync::Arc;
use wasmi_arena::ArenaIndex;

/// A raw index to a element segment entity.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ElementSegmentIdx(u32);

impl ArenaIndex for ElementSegmentIdx {
fn into_usize(self) -> usize {
self.0 as usize
}

fn from_usize(value: usize) -> Self {
let value = value.try_into().unwrap_or_else(|error| {
panic!("index {value} is out of bounds as element segment index: {error}")
});
Self(value)
}
}

/// A Wasm data segment reference.
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct ElementSegment(Stored<ElementSegmentIdx>);

impl ElementSegment {
/// Creates a new linear memory reference.
pub fn from_inner(stored: Stored<ElementSegmentIdx>) -> Self {
Self(stored)
}

/// Returns the underlying stored representation.
pub fn as_inner(&self) -> &Stored<ElementSegmentIdx> {
&self.0
}

/// Allocates a new [`ElementSegment`] on the store.
///
/// # Errors
///
/// If more than [`u32::MAX`] much linear memory is allocated.
pub fn new(mut ctx: impl AsContextMut, segment: &module::ElementSegment) -> Self {
let entity = ElementSegmentEntity::from(segment);
ctx.as_context_mut()
.store
.inner
.alloc_element_segment(entity)
}
}

/// An instantiated [`ElementSegmentEntity`].
///
/// # Note
///
/// With the `bulk-memory` Wasm proposal it is possible to interact
/// with element segments at runtime. Therefore Wasm instances now have
/// a need to have an instantiated representation of data segments.
#[derive(Debug)]
pub struct ElementSegmentEntity {
/// The underlying items of the instance element segment.
///
/// # Note
///
/// These items are just readable after instantiation.
/// Using Wasm `elem.drop` simply replaces the instance
/// with an empty one.
items: Option<Arc<[Option<FuncIdx>]>>,
}

impl From<&'_ module::ElementSegment> for ElementSegmentEntity {
fn from(segment: &'_ module::ElementSegment) -> Self {
match segment.kind() {
module::ElementSegmentKind::Passive => Self {
items: Some(segment.clone_items()),
},
module::ElementSegmentKind::Active(_) => Self::empty(),
}
}
}

impl ElementSegmentEntity {
/// Create an empty [`ElementSegmentEntity`] representing dropped element segments.
fn empty() -> Self {
Self { items: None }
}

/// Returns the items of the [`ElementSegmentEntity`].
pub fn items(&self) -> &[Option<FuncIdx>] {
self.items
.as_ref()
.map(|items| &items[..])
.unwrap_or_else(|| &[])
}

/// Drops the items of the [`ElementSegmentEntity`].
pub fn drop_items(&mut self) {
self.items = None;
}
}
9 changes: 9 additions & 0 deletions crates/wasmi/src/engine/bytecode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ mod tests;
pub use self::utils::{
BranchOffset,
BranchParams,
DataSegmentIdx,
DropKeep,
DropKeepError,
ElementSegmentIdx,
FuncIdx,
GlobalIdx,
LocalDepth,
Expand Down Expand Up @@ -70,6 +72,13 @@ pub enum Instruction {
I64Store32(Offset),
MemorySize,
MemoryGrow,
MemoryFill,
MemoryCopy,
MemoryInit(DataSegmentIdx),
DataDrop(DataSegmentIdx),
TableCopy,
TableInit(ElementSegmentIdx),
ElemDrop(ElementSegmentIdx),
Const(UntypedValue),
I32Eqz,
I32Eq,
Expand Down
50 changes: 49 additions & 1 deletion crates/wasmi/src/engine/bytecode/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ impl LocalDepth {
///
/// Refers to a global variable of a [`Store`].
///
/// [`Store`]: [`crate::v2::Store`]
/// [`Store`]: [`crate::Store`]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct GlobalIdx(u32);
Expand All @@ -148,6 +148,54 @@ impl GlobalIdx {
}
}

/// A data segment index.
///
/// # Note
///
/// Refers to a data segment of a [`Store`].
///
/// [`Store`]: [`crate::Store`]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct DataSegmentIdx(u32);

impl From<u32> for DataSegmentIdx {
fn from(index: u32) -> Self {
Self(index)
}
}

impl DataSegmentIdx {
/// Returns the inner `u32` index.
pub fn into_inner(self) -> u32 {
self.0
}
}

/// An element segment index.
///
/// # Note
///
/// Refers to a data segment of a [`Store`].
///
/// [`Store`]: [`crate::Store`]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct ElementSegmentIdx(u32);

impl From<u32> for ElementSegmentIdx {
fn from(index: u32) -> Self {
Self(index)
}
}

impl ElementSegmentIdx {
/// Returns the inner `u32` index.
pub fn into_inner(self) -> u32 {
self.0
}
}

/// A linear memory access offset.
///
/// # Note
Expand Down
93 changes: 84 additions & 9 deletions crates/wasmi/src/engine/cache.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use crate::{
element::{ElementSegment, ElementSegmentEntity},
instance::InstanceEntity,
memory::DataSegment,
module::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX},
table::TableEntity,
Func,
Instance,
Memory,
Expand All @@ -9,6 +13,8 @@ use crate::{
use core::ptr::NonNull;
use wasmi_core::UntypedValue;

use super::bytecode::{DataSegmentIdx, ElementSegmentIdx};

/// A cache for frequently used entities of an [`Instance`].
#[derive(Debug)]
pub struct InstanceCache {
Expand Down Expand Up @@ -63,21 +69,89 @@ impl InstanceCache {
self.set_instance(instance);
}

/// Loads the [`DataSegment`] at `index` of the currently used [`Instance`].
///
/// # Panics
///
/// If there is no [`DataSegment`] for the [`Instance`] at the `index`.
#[inline]
pub fn get_data_segment(&mut self, ctx: &mut StoreInner, index: u32) -> DataSegment {
let instance = self.instance();
ctx.resolve_instance(self.instance())
.get_data_segment(index)
.unwrap_or_else(|| {
panic!("missing data segment ({index:?}) for instance: {instance:?}",)
})
}

/// Loads the [`ElementSegment`] at `index` of the currently used [`Instance`].
///
/// # Panics
///
/// If there is no [`ElementSegment`] for the [`Instance`] at the `index`.
#[inline]
pub fn get_element_segment(
&mut self,
ctx: &mut StoreInner,
index: ElementSegmentIdx,
) -> ElementSegment {
let instance = self.instance();
ctx.resolve_instance(self.instance())
.get_element_segment(index.into_inner())
.unwrap_or_else(|| {
panic!("missing element segment ({index:?}) for instance: {instance:?}",)
})
}

/// Loads the [`DataSegment`] at `index` of the currently used [`Instance`].
///
/// # Panics
///
/// If there is no [`DataSegment`] for the [`Instance`] at the `index`.
#[inline]
pub fn get_default_memory_and_data_segment<'a>(
&mut self,
ctx: &'a mut StoreInner,
segment: DataSegmentIdx,
) -> (&'a mut [u8], &'a [u8]) {
let mem = self.default_memory(ctx);
let seg = self.get_data_segment(ctx, segment.into_inner());
let (memory, segment) = ctx.resolve_memory_mut_and_data_segment(&mem, &seg);
(memory.data_mut(), segment.bytes())
}

/// Loads the [`ElementSegment`] at `index` of the currently used [`Instance`].
///
/// # Panics
///
/// If there is no [`ElementSegment`] for the [`Instance`] at the `index`.
#[inline]
pub fn get_default_table_and_element_segment<'a>(
&mut self,
ctx: &'a mut StoreInner,
segment: ElementSegmentIdx,
) -> (
&'a InstanceEntity,
&'a mut TableEntity,
&'a ElementSegmentEntity,
) {
let tab = self.default_table(ctx);
let seg = self.get_element_segment(ctx, segment);
let inst = self.instance();
ctx.resolve_instance_table_element(inst, &tab, &seg)
}

/// Loads the default [`Memory`] of the currently used [`Instance`].
///
/// # Panics
///
/// If the currently used [`Instance`] does not have a default linear memory.
fn load_default_memory(&mut self, ctx: &StoreInner) -> Memory {
let instance = self.instance();
let default_memory = ctx
.resolve_instance(self.instance())
.resolve_instance(instance)
.get_memory(DEFAULT_MEMORY_INDEX)
.unwrap_or_else(|| {
panic!(
"missing default linear memory for instance: {:?}",
self.instance
)
});
.unwrap_or_else(|| panic!("missing default linear memory for instance: {instance:?}",));
self.default_memory = Some(default_memory);
default_memory
}
Expand All @@ -88,10 +162,11 @@ impl InstanceCache {
///
/// If the currently used [`Instance`] does not have a default table.
fn load_default_table(&mut self, ctx: &StoreInner) -> Table {
let instance = self.instance();
let default_table = ctx
.resolve_instance(self.instance())
.resolve_instance(instance)
.get_table(DEFAULT_TABLE_INDEX)
.unwrap_or_else(|| panic!("missing default table for instance: {:?}", self.instance));
.unwrap_or_else(|| panic!("missing default table for instance: {instance:?}"));
self.default_table = Some(default_table);
default_table
}
Expand Down
17 changes: 16 additions & 1 deletion crates/wasmi/src/engine/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ pub struct Config {
saturating_float_to_int: bool,
/// Is `true` if the [`multi-value`] Wasm proposal is enabled.
multi_value: bool,
/// Is `true` if the [`bulk-memory`] Wasm proposal is enabled.
bulk_memory: bool,
}

impl Default for Config {
Expand All @@ -32,6 +34,7 @@ impl Default for Config {
sign_extension: true,
saturating_float_to_int: true,
multi_value: true,
bulk_memory: true,
}
}
}
Expand Down Expand Up @@ -112,15 +115,27 @@ impl Config {
self
}

/// Enable or disable the [`bulk-memory`] Wasm proposal for the [`Config`].
///
/// # Note
///
/// Enabled by default.
///
/// [`multi-value`]: https:/WebAssembly/bulk-memory-operations
pub fn wasm_bulk_memory(&mut self, enable: bool) -> &mut Self {
self.bulk_memory = enable;
self
}

/// Returns the [`WasmFeatures`] represented by the [`Config`].
pub fn wasm_features(&self) -> WasmFeatures {
WasmFeatures {
multi_value: self.multi_value,
mutable_global: self.mutable_global,
saturating_float_to_int: self.saturating_float_to_int,
sign_extension: self.sign_extension,
bulk_memory: self.bulk_memory,
reference_types: false,
bulk_memory: false,
component_model: false,
simd: false,
relaxed_simd: false,
Expand Down
Loading

0 comments on commit 5f9d81a

Please sign in to comment.