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

Implement bulk-memory Wasm proposal #628

Merged
merged 49 commits into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
49 commits
Select commit Hold shift click to select a range
9bf645e
add bulk-memory-operations Wasm spec tests
Robbepop Jan 21, 2023
8274fc3
add bulk_memory flag to Config
Robbepop Jan 21, 2023
adf388c
refactor Wasm spec test suite runners
Robbepop Jan 21, 2023
7db25a5
refactor define_X_tests macros
Robbepop Jan 21, 2023
18834ef
add comment about mutable-global
Robbepop Jan 21, 2023
1331e14
remove ModuleError::Unsupported
Robbepop Jan 21, 2023
13e7592
cleanup after last commit to use panics instead of unsupported error
Robbepop Jan 21, 2023
12cf30a
Update init_expr.rs
Robbepop Jan 21, 2023
c5dd65b
Merge branch 'master' into rf-implement-bulk-memory
Robbepop Jan 21, 2023
4f05fa9
implement initial skeleton for passive data segments
Robbepop Jan 21, 2023
6f5af22
export DataSegmentKind from the data module and use it
Robbepop Jan 21, 2023
3835643
refactor data segments minimally
Robbepop Jan 21, 2023
aec8b1e
add skeleton for passive Wasm element segments
Robbepop Jan 21, 2023
548d6cb
refactor InitExpr
Robbepop Jan 21, 2023
2c0d4c3
add InstanceDataSegment to InstanceEntity
Robbepop Jan 21, 2023
fe42f6e
add data segments to instance and store
Robbepop Jan 22, 2023
aaea98c
implement memory.init Wasm instruction
Robbepop Jan 22, 2023
1f5a1d5
replace todo!() with unimplemented!()
Robbepop Jan 22, 2023
6b0854b
fix doc links
Robbepop Jan 22, 2023
24d31c2
implement DataDrop wasmi instruction
Robbepop Jan 22, 2023
30d5225
add missing docs
Robbepop Jan 22, 2023
4035267
remove commented out line
Robbepop Jan 22, 2023
5ce1387
add wasmi memory.fill instruction
Robbepop Jan 22, 2023
ff2b71d
implement wasmi memory.copy instruction
Robbepop Jan 22, 2023
0617781
remove unused APIs
Robbepop Jan 22, 2023
c1de7ef
fix bug in bulk-memory instructions implemented so far
Robbepop Jan 22, 2023
5088b4d
move memory query down
Robbepop Jan 22, 2023
45b515d
fix bug in some bulk-memory instruction impls
Robbepop Jan 22, 2023
2f95fcd
adjust InitExpr and module::ElementSegment
Robbepop Jan 22, 2023
716321e
fix bugs
Robbepop Jan 24, 2023
28d4d5d
add element segments to the store
Robbepop Jan 24, 2023
1d0c0b3
implement table.init and elem.drop Wasm instructions
Robbepop Jan 24, 2023
c13b9a3
update wast from 0.44 to 0.45
Robbepop Jan 24, 2023
076780c
update wast 0.45 -> 0.46
Robbepop Jan 24, 2023
377dc1d
update wast 0.46 -> 0.52
Robbepop Jan 24, 2023
087f5fb
implement Wasm table.copy instruction
Robbepop Jan 24, 2023
127c929
implement user facing Table::copy API
Robbepop Jan 24, 2023
d16827c
update Wasm testsuite revision
Robbepop Jan 24, 2023
1cad262
cleanup in tests
Robbepop Jan 24, 2023
95e469d
fix typo
Robbepop Jan 24, 2023
a2b29d4
fix Wasm spectest suite test case
Robbepop Jan 24, 2023
cf047db
remove some forwarding methods in Store
Robbepop Jan 25, 2023
04cc114
Merge branch 'master' into rf-implement-bulk-memory
Robbepop Jan 25, 2023
a1a5d61
fix remaining bugs
Robbepop Jan 25, 2023
49668ce
Merge branch 'master' into rf-implement-bulk-memory
Robbepop Jan 25, 2023
ea1988c
fix code after merge
Robbepop Jan 25, 2023
da2d5c8
rename tests
Robbepop Jan 25, 2023
5e1518d
expect failure for all reference-types related tests
Robbepop Jan 25, 2023
e9c25d3
fix internal doc link
Robbepop Jan 25, 2023
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
Prev Previous commit
Next Next commit
implement table.init and elem.drop Wasm instructions
  • Loading branch information
Robbepop committed Jan 24, 2023
commit 1d0c0b3224d2bdd8d28bbc30b079e3c0f2589f43
3 changes: 3 additions & 0 deletions crates/wasmi/src/engine/bytecode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 11,7 @@ pub use self::utils::{
DataSegmentIdx,
DropKeep,
DropKeepError,
ElementSegmentIdx,
FuncIdx,
GlobalIdx,
LocalDepth,
Expand Down Expand Up @@ -75,6 76,8 @@ pub enum Instruction {
MemoryCopy,
MemoryInit(DataSegmentIdx),
DataDrop(DataSegmentIdx),
TableInit(ElementSegmentIdx),
ElemDrop(ElementSegmentIdx),
Const(UntypedValue),
I32Eqz,
I32Eq,
Expand Down
24 changes: 24 additions & 0 deletions crates/wasmi/src/engine/bytecode/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 172,30 @@ impl DataSegmentIdx {
}
}

/// 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
42 changes: 41 additions & 1 deletion crates/wasmi/src/engine/cache.rs
Original file line number Diff line number Diff line change
@@ -1,6 1,9 @@
use crate::{
element::ElementSegment,
instance::InstanceEntity,
memory::DataSegment,
module::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX},
module::{FuncIdx, DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX},
table::TableEntity,
Func,
Instance,
Memory,
Expand Down Expand Up @@ -79,6 82,21 @@ impl InstanceCache {
})
}

/// 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: u32) -> ElementSegment {
let instance = self.instance();
ctx.resolve_instance(self.instance())
.get_element_segment(index)
.unwrap_or_else(|| {
panic!("missing element segment ({index:?}) for instance: {instance:?}",)
})
}

/// Loads the [`DataSegment`] at `index` of the currently used [`Instance`].
///
/// # Panics
Expand All @@ -96,6 114,28 @@ impl InstanceCache {
(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: u32,
) -> (
&'a InstanceEntity,
&'a mut TableEntity,
&'a [Option<FuncIdx>],
) {
let inst = self.instance();
let tab = self.default_table(ctx);
let seg = self.get_element_segment(ctx, segment);
let (instance, table, segment) = ctx.resolve_instance_table_element(inst, tab, seg);
(instance, table, segment.items())
}

/// Loads the default [`Memory`] of the currently used [`Instance`].
///
/// # Panics
Expand Down
42 changes: 41 additions & 1 deletion crates/wasmi/src/engine/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 3,7 @@ use super::{
bytecode::{
BranchParams,
DataSegmentIdx,
ElementSegmentIdx,
FuncIdx,
GlobalIdx,
Instruction,
Expand Down Expand Up @@ -147,6 148,8 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> {
Instr::MemoryCopy => self.visit_memory_copy()?,
Instr::MemoryInit(segment) => self.visit_memory_init(segment)?,
Instr::DataDrop(segment) => self.visit_data_drop(segment),
Instr::TableInit(segment) => self.visit_table_init(segment)?,
Instr::ElemDrop(segment) => self.visit_element_drop(segment),
Instr::Const(bytes) => self.visit_const(bytes),
Instr::I32Eqz => self.visit_i32_eqz(),
Instr::I32Eq => self.visit_i32_eq(),
Expand Down Expand Up @@ -659,7 662,7 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> {
.ok_or(TrapCode::MemoryOutOfBounds)?;
let data = data
.get(src_offset..)
.and_then(|memory| memory.get(..n))
.and_then(|data| data.get(..n))
.ok_or(TrapCode::MemoryOutOfBounds)?;
memory.copy_from_slice(data);
self.next_instr();
Expand All @@ -674,6 677,43 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> {
self.next_instr();
}

fn visit_table_init(&mut self, segment: ElementSegmentIdx) -> Result<(), TrapCode> {
// The `n`, `s` and `d` variable bindings are extracted from the Wasm specification.
let (d, s, n) = self.value_stack.pop3();
let n = u32::from(n);
let src_offset = u32::from(s);
let dst_offset = u32::from(d);
let (instance, table, element) = self
.cache
.get_default_table_and_element_segment(self.ctx, segment.into_inner());
// These accesses just perform the bounds checks required by the Wasm spec.
dst_offset
.checked_add(n)
.filter(|&offset| offset < table.size())
.ok_or(TrapCode::TableOutOfBounds)?;
let element = element
.get(src_offset as usize..)
.and_then(|element| element.get(..n as usize))
.ok_or(TrapCode::TableOutOfBounds)?;
for (i, func_idx) in (dst_offset..).zip(element) {
let funcref = func_idx.and_then(|index| instance.get_func(index.into_u32()));
table.set(i, funcref).unwrap_or_else(|_error| {
// Note: We panic here since we already performed the bounds check.
panic!("unexpected out of bounds at {i} for table: {table:?}")
});
}
self.next_instr();
Ok(())
}

fn visit_element_drop(&mut self, segment_index: ElementSegmentIdx) {
let segment = self
.cache
.get_element_segment(self.ctx, segment_index.into_inner());
self.ctx.resolve_element_segment_mut(segment).drop_items();
self.next_instr();
}

fn visit_i32_load(&mut self, offset: Offset) -> Result<(), TrapCode> {
self.execute_load_extend(offset, UntypedValue::i32_load)
}
Expand Down
46 changes: 25 additions & 21 deletions crates/wasmi/src/engine/func_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 27,7 @@ pub use self::{
};
use super::{DropKeep, FuncBody, Instruction};
use crate::{
engine::bytecode::{BranchParams, DataSegmentIdx, Offset},
engine::bytecode::{BranchParams, DataSegmentIdx, ElementSegmentIdx, Offset},
module::{
BlockType,
FuncIdx,
Expand Down Expand Up @@ -801,7 801,7 @@ impl<'parser> FuncBuilder<'parser> {
let global_idx = GlobalIdx(global_idx);
builder.stack_height.push();
let (global_type, init_value) = builder.res.get_global(global_idx);
let instr = match init_value.and_then(InitExpr::into_const) {
let instr = match init_value.and_then(InitExpr::to_const) {
Some(value) if global_type.mutability().is_const() => Instruction::constant(value),
_ => Instruction::GlobalGet(global_idx.into_u32().into()),
};
Expand Down Expand Up @@ -1265,31 1265,35 @@ impl<'parser> FuncBuilder<'parser> {
})
}

/// Translate a Wasm `table.init` instruction.
pub fn translate_table_init(
&mut self,
_segment_index: u32,
_table_index: u32,
segment_index: u32,
table_index: u32,
) -> Result<(), TranslationError> {
self.translate_if_reachable(|_builder| {
// debug_assert_eq!(table_index, DEFAULT_MEMORY_INDEX);
// let memory_index = TableIdx(table_index);
// builder.stack_height.pop3();
// builder
// .alloc
// .inst_builder
// .push_inst(Instruction::TableInit(ElementSegmentIdx::from(segment_index)));
todo!()
self.translate_if_reachable(|builder| {
debug_assert_eq!(table_index, DEFAULT_MEMORY_INDEX);
builder.stack_height.pop3();
builder
.alloc
.inst_builder
.push_inst(Instruction::TableInit(ElementSegmentIdx::from(
segment_index,
)));
Ok(())
})
}

pub fn translate_elem_drop(&mut self, _segment_index: u32) -> Result<(), TranslationError> {
self.translate_if_reachable(|_builder| {
// let segment_index = ElementSegmentIdx::from(segment_index);
// builder
// .alloc
// .inst_builder
// .push_inst(Instruction::ElemDrop { segment_index });
todo!()
/// Translate a Wasm `elem.drop` instruction.
pub fn translate_elem_drop(&mut self, segment_index: u32) -> Result<(), TranslationError> {
self.translate_if_reachable(|builder| {
builder
.alloc
.inst_builder
.push_inst(Instruction::ElemDrop(ElementSegmentIdx::from(
segment_index,
)));
Ok(())
})
}

Expand Down
18 changes: 17 additions & 1 deletion crates/wasmi/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 11,7 @@ use super::{
Table,
};
use crate::{
element::ElementSegment,
func::FuncError,
memory::DataSegment,
module::FuncIdx,
Expand Down Expand Up @@ -57,6 58,7 @@ pub struct InstanceEntity {
globals: Box<[Global]>,
exports: BTreeMap<Box<str>, Extern>,
data_segments: Box<[DataSegment]>,
elem_segments: Box<[ElementSegment]>,
}

impl InstanceEntity {
Expand All @@ -71,6 73,7 @@ impl InstanceEntity {
globals: [].into(),
exports: BTreeMap::new(),
data_segments: [].into(),
elem_segments: [].into(),
}
}

Expand Down Expand Up @@ -109,11 112,16 @@ impl InstanceEntity {
self.func_types.get(index as usize).copied()
}

/// Returns the data segment at the `index` if any.
/// Returns the [`DataSegment`] at the `index` if any.
pub(crate) fn get_data_segment(&self, index: u32) -> Option<DataSegment> {
self.data_segments.get(index as usize).copied()
}

/// Returns the [`ElementSegment`] at the `index` if any.
pub(crate) fn get_element_segment(&self, index: u32) -> Option<ElementSegment> {
self.elem_segments.get(index as usize).copied()
}

/// Returns the value exported to the given `name` if any.
pub(crate) fn get_export(&self, name: &str) -> Option<Extern> {
self.exports.get(name).copied()
Expand Down Expand Up @@ -241,6 249,7 @@ pub struct InstanceEntityBuilder {
start_fn: Option<FuncIdx>,
exports: BTreeMap<Box<str>, Extern>,
data_segments: Vec<DataSegment>,
elem_segments: Vec<ElementSegment>,
}

impl InstanceEntityBuilder {
Expand Down Expand Up @@ -280,6 289,7 @@ impl InstanceEntityBuilder {
start_fn: None,
exports: BTreeMap::default(),
data_segments: Vec::new(),
elem_segments: Vec::new(),
}
}

Expand Down Expand Up @@ -398,6 408,11 @@ impl InstanceEntityBuilder {
self.data_segments.push(segment);
}

/// Pushes the [`ElementSegment`] to the [`InstanceEntity`] under construction.
pub fn push_element_segment(&mut self, segment: ElementSegment) {
self.elem_segments.push(segment);
}

/// Finishes constructing the [`InstanceEntity`].
pub fn finish(self) -> InstanceEntity {
InstanceEntity {
Expand All @@ -409,6 424,7 @@ impl InstanceEntityBuilder {
globals: self.globals.into(),
exports: self.exports,
data_segments: self.data_segments.into(),
elem_segments: self.elem_segments.into(),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmi/src/module/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 84,7 @@ impl TryFrom<wasmparser::Element<'_>> for ElementSegment {
.map(|item| {
let func_ref = match item? {
wasmparser::ElementItem::Func(func_idx) => Some(FuncIdx(func_idx)),
wasmparser::ElementItem::Expr(expr) => InitExpr::new(expr).into_elemexpr(),
wasmparser::ElementItem::Expr(expr) => InitExpr::new(expr).to_elemexpr(),
};
<Result<_, ModuleError>>::Ok(func_ref)
})
Expand Down
6 changes: 3 additions & 3 deletions crates/wasmi/src/module/init_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 47,7 @@ impl InitExpr {
/// # Panics
///
/// If a non Wasm `elemexpr` operand is encountered.
pub fn into_elemexpr(&self) -> Option<FuncIdx> {
pub fn to_elemexpr(&self) -> Option<FuncIdx> {
match self.op {
InitExprOperand::RefNull => None,
InitExprOperand::FuncRef(func_index) => Some(FuncIdx(func_index)),
Expand All @@ -64,7 64,7 @@ impl InitExpr {
/// # Panics
///
/// If a non-const expression operand is encountered.
pub fn into_const(&self) -> Option<Value> {
pub fn to_const(&self) -> Option<Value> {
match self.op {
InitExprOperand::Const(value) => Some(value),
// Note: We do not need to handle `global.get` since
Expand All @@ -82,7 82,7 @@ impl InitExpr {
/// # Panics
///
/// If a non-const expression operand is encountered.
pub fn into_const_with_context(&self, global_get: impl Fn(u32) -> Value) -> Value {
pub fn to_const_with_context(&self, global_get: impl Fn(u32) -> Value) -> Value {
match self.op {
InitExprOperand::Const(value) => value,
InitExprOperand::GlobalGet(index) => global_get(index.into_u32()),
Expand Down
Loading