Skip to content

Commit 674a620

Browse files
authored
Implement data.drop and memory.init and get the rest of the bulk memory spec tests passing (#1264)
* Enable the already-passing `bulk-memoryoperations/imports.wast` test * Implement support for the `memory.init` instruction and passive data This adds support for passive data segments and the `memory.init` instruction from the bulk memory operations proposal. Passive data segments are stored on the Wasm module and then `memory.init` instructions copy their contents into memory. * Implement the `data.drop` instruction This allows wasm modules to deallocate passive data segments that it doesn't need anymore. We keep track of which segments have not been dropped on an `Instance` and when dropping them, remove the entry from the instance's hash map. The module always needs all of the segments for new instantiations. * Enable final bulk memory operations spec test This requires special casing an expected error message for an `assert_trap`, since the expected error message contains the index of an uninitialized table element, but our trap implementation doesn't save that diagnostic information and shepherd it out.
1 parent 11510ec commit 674a620

File tree

10 files changed

+237
-42
lines changed

10 files changed

+237
-42
lines changed

build.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,6 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool {
196196
("reference_types", "table_copy_on_imported_tables") => return false,
197197
("reference_types", _) => return true,
198198

199-
// Still working on implementing these. See #928
200-
("bulk_memory_operations", "bulk")
201-
| ("bulk_memory_operations", "data")
202-
| ("bulk_memory_operations", "memory_init")
203-
| ("bulk_memory_operations", "imports") => return true,
204-
205199
_ => {}
206200
},
207201
_ => panic!("unrecognized strategy"),

crates/environ/src/data_structures.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub mod entity {
2222

2323
pub mod wasm {
2424
pub use cranelift_wasm::{
25-
get_vmctx_value_label, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex,
25+
get_vmctx_value_label, DataIndex, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex,
2626
DefinedTableIndex, ElemIndex, FuncIndex, Global, GlobalIndex, GlobalInit, Memory,
2727
MemoryIndex, SignatureIndex, Table, TableElementType, TableIndex,
2828
};

crates/environ/src/func_environ.rs

Lines changed: 106 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,17 @@ impl BuiltinFunctionIndex {
7272
pub const fn get_imported_memory_fill_index() -> Self {
7373
Self(10)
7474
}
75+
/// Returns an index for wasm's `memory.init` instruction.
76+
pub const fn get_memory_init_index() -> Self {
77+
Self(11)
78+
}
79+
/// Returns an index for wasm's `data.drop` instruction.
80+
pub const fn get_data_drop_index() -> Self {
81+
Self(12)
82+
}
7583
/// Returns the total number of builtin functions.
7684
pub const fn builtin_functions_total_number() -> u32 {
77-
11
85+
13
7886
}
7987

8088
/// Return the index as an u32 number.
@@ -120,6 +128,12 @@ pub struct FuncEnvironment<'module_environment> {
120128
/// (it's the same for both local and imported memories).
121129
memory_fill_sig: Option<ir::SigRef>,
122130

131+
/// The external function signature for implementing wasm's `memory.init`.
132+
memory_init_sig: Option<ir::SigRef>,
133+
134+
/// The external function signature for implementing wasm's `data.drop`.
135+
data_drop_sig: Option<ir::SigRef>,
136+
123137
/// Offsets to struct fields accessed by JIT code.
124138
offsets: VMOffsets,
125139
}
@@ -140,6 +154,8 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
140154
elem_drop_sig: None,
141155
memory_copy_sig: None,
142156
memory_fill_sig: None,
157+
memory_init_sig: None,
158+
data_drop_sig: None,
143159
offsets: VMOffsets::new(target_config.pointer_bytes(), module),
144160
}
145161
}
@@ -423,6 +439,58 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
423439
}
424440
}
425441

442+
fn get_memory_init_sig(&mut self, func: &mut Function) -> ir::SigRef {
443+
let sig = self.memory_init_sig.unwrap_or_else(|| {
444+
func.import_signature(Signature {
445+
params: vec![
446+
AbiParam::special(self.pointer_type(), ArgumentPurpose::VMContext),
447+
// Memory index.
448+
AbiParam::new(I32),
449+
// Data index.
450+
AbiParam::new(I32),
451+
// Destination address.
452+
AbiParam::new(I32),
453+
// Source index within the data segment.
454+
AbiParam::new(I32),
455+
// Length.
456+
AbiParam::new(I32),
457+
// Source location.
458+
AbiParam::new(I32),
459+
],
460+
returns: vec![],
461+
call_conv: self.target_config.default_call_conv,
462+
})
463+
});
464+
self.memory_init_sig = Some(sig);
465+
sig
466+
}
467+
468+
fn get_memory_init_func(&mut self, func: &mut Function) -> (ir::SigRef, BuiltinFunctionIndex) {
469+
let sig = self.get_memory_init_sig(func);
470+
(sig, BuiltinFunctionIndex::get_memory_init_index())
471+
}
472+
473+
fn get_data_drop_sig(&mut self, func: &mut Function) -> ir::SigRef {
474+
let sig = self.data_drop_sig.unwrap_or_else(|| {
475+
func.import_signature(Signature {
476+
params: vec![
477+
AbiParam::special(self.pointer_type(), ArgumentPurpose::VMContext),
478+
// Data index.
479+
AbiParam::new(I32),
480+
],
481+
returns: vec![],
482+
call_conv: self.target_config.default_call_conv,
483+
})
484+
});
485+
self.data_drop_sig = Some(sig);
486+
sig
487+
}
488+
489+
fn get_data_drop_func(&mut self, func: &mut Function) -> (ir::SigRef, BuiltinFunctionIndex) {
490+
let sig = self.get_data_drop_sig(func);
491+
(sig, BuiltinFunctionIndex::get_data_drop_index())
492+
}
493+
426494
/// Translates load of builtin function and returns a pair of values `vmctx`
427495
/// and address of the loaded function.
428496
fn translate_load_builtin_function_address(
@@ -1076,23 +1144,47 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
10761144

10771145
fn translate_memory_init(
10781146
&mut self,
1079-
_pos: FuncCursor,
1080-
_index: MemoryIndex,
1147+
mut pos: FuncCursor,
1148+
memory_index: MemoryIndex,
10811149
_heap: ir::Heap,
1082-
_seg_index: u32,
1083-
_dst: ir::Value,
1084-
_src: ir::Value,
1085-
_len: ir::Value,
1150+
seg_index: u32,
1151+
dst: ir::Value,
1152+
src: ir::Value,
1153+
len: ir::Value,
10861154
) -> WasmResult<()> {
1087-
Err(WasmError::Unsupported(
1088-
"bulk memory: `memory.init`".to_string(),
1089-
))
1155+
let (func_sig, func_idx) = self.get_memory_init_func(&mut pos.func);
1156+
1157+
let memory_index_arg = pos.ins().iconst(I32, memory_index.index() as i64);
1158+
let seg_index_arg = pos.ins().iconst(I32, seg_index as i64);
1159+
let src_loc = pos.srcloc();
1160+
let src_loc_arg = pos.ins().iconst(I32, src_loc.bits() as i64);
1161+
1162+
let (vmctx, func_addr) = self.translate_load_builtin_function_address(&mut pos, func_idx);
1163+
1164+
pos.ins().call_indirect(
1165+
func_sig,
1166+
func_addr,
1167+
&[
1168+
vmctx,
1169+
memory_index_arg,
1170+
seg_index_arg,
1171+
dst,
1172+
src,
1173+
len,
1174+
src_loc_arg,
1175+
],
1176+
);
1177+
1178+
Ok(())
10901179
}
10911180

1092-
fn translate_data_drop(&mut self, _pos: FuncCursor, _seg_index: u32) -> WasmResult<()> {
1093-
Err(WasmError::Unsupported(
1094-
"bulk memory: `data.drop`".to_string(),
1095-
))
1181+
fn translate_data_drop(&mut self, mut pos: FuncCursor, seg_index: u32) -> WasmResult<()> {
1182+
let (func_sig, func_idx) = self.get_data_drop_func(&mut pos.func);
1183+
let seg_index_arg = pos.ins().iconst(I32, seg_index as i64);
1184+
let (vmctx, func_addr) = self.translate_load_builtin_function_address(&mut pos, func_idx);
1185+
pos.ins()
1186+
.call_indirect(func_sig, func_addr, &[vmctx, seg_index_arg]);
1187+
Ok(())
10961188
}
10971189

10981190
fn translate_table_size(

crates/environ/src/module.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ use crate::WASM_MAX_PAGES;
55
use cranelift_codegen::ir;
66
use cranelift_entity::{EntityRef, PrimaryMap};
77
use cranelift_wasm::{
8-
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, ElemIndex,
9-
FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, Table, TableIndex,
8+
DataIndex, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex,
9+
ElemIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, Table,
10+
TableIndex,
1011
};
1112
use indexmap::IndexMap;
1213
use more_asserts::assert_ge;
1314
use std::collections::HashMap;
14-
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
15+
use std::sync::{
16+
atomic::{AtomicUsize, Ordering::SeqCst},
17+
Arc,
18+
};
1519

1620
/// A WebAssembly table initializer.
1721
#[derive(Clone, Debug, Hash)]
@@ -168,6 +172,9 @@ pub struct Module {
168172
/// WebAssembly passive elements.
169173
pub passive_elements: HashMap<ElemIndex, Box<[FuncIndex]>>,
170174

175+
/// WebAssembly passive data segments.
176+
pub passive_data: HashMap<DataIndex, Arc<[u8]>>,
177+
171178
/// WebAssembly table initializers.
172179
pub func_names: HashMap<FuncIndex, String>,
173180
}
@@ -223,6 +230,7 @@ impl Module {
223230
start_func: None,
224231
table_elements: Vec::new(),
225232
passive_elements: HashMap::new(),
233+
passive_data: HashMap::new(),
226234
func_names: HashMap::new(),
227235
local: ModuleLocal {
228236
num_imported_funcs: 0,

crates/environ/src/module_environ.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ use cranelift_entity::PrimaryMap;
88
use cranelift_wasm::{
99
self, translate_module, DataIndex, DefinedFuncIndex, ElemIndex, FuncIndex, Global, GlobalIndex,
1010
Memory, MemoryIndex, ModuleTranslationState, SignatureIndex, Table, TableIndex,
11-
TargetEnvironment, WasmError, WasmResult,
11+
TargetEnvironment, WasmResult,
1212
};
1313
use std::convert::TryFrom;
14+
use std::sync::Arc;
1415

1516
/// Contains function data: byte code and its offset in the module.
1617
#[derive(Hash)]
@@ -391,18 +392,21 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
391392
}
392393

393394
fn reserve_passive_data(&mut self, count: u32) -> WasmResult<()> {
394-
self.result.module.passive_elements.reserve(count as usize);
395+
self.result.module.passive_data.reserve(count as usize);
395396
Ok(())
396397
}
397398

398-
fn declare_passive_data(
399-
&mut self,
400-
_data_index: DataIndex,
401-
_data: &'data [u8],
402-
) -> WasmResult<()> {
403-
Err(WasmError::Unsupported(
404-
"bulk memory: passive data".to_string(),
405-
))
399+
fn declare_passive_data(&mut self, data_index: DataIndex, data: &'data [u8]) -> WasmResult<()> {
400+
let old = self
401+
.result
402+
.module
403+
.passive_data
404+
.insert(data_index, Arc::from(data));
405+
debug_assert!(
406+
old.is_none(),
407+
"a module can't have duplicate indices, this would be a cranelift-wasm bug"
408+
);
409+
Ok(())
406410
}
407411

408412
fn declare_func_name(&mut self, func_index: FuncIndex, name: &'data str) -> WasmResult<()> {

crates/runtime/src/instance.rs

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ use std::{mem, ptr, slice};
2929
use thiserror::Error;
3030
use wasmtime_environ::entity::{packed_option::ReservedValue, BoxedSlice, EntityRef, PrimaryMap};
3131
use wasmtime_environ::wasm::{
32-
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, ElemIndex,
33-
FuncIndex, GlobalIndex, GlobalInit, MemoryIndex, SignatureIndex, TableIndex,
32+
DataIndex, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex,
33+
ElemIndex, FuncIndex, GlobalIndex, GlobalInit, MemoryIndex, SignatureIndex, TableIndex,
3434
};
3535
use wasmtime_environ::{ir, DataInitializer, Module, TableElements, VMOffsets};
3636

@@ -92,6 +92,10 @@ pub(crate) struct Instance {
9292
/// empty slice.
9393
passive_elements: RefCell<HashMap<ElemIndex, Box<[VMCallerCheckedAnyfunc]>>>,
9494

95+
/// Passive data segments from our module. As `data.drop`s happen, entries
96+
/// get removed. A missing entry is considered equivalent to an empty slice.
97+
passive_data: RefCell<HashMap<DataIndex, Arc<[u8]>>>,
98+
9599
/// Pointers to functions in executable memory.
96100
finished_functions: BoxedSlice<DefinedFuncIndex, *mut [VMFunctionBody]>,
97101

@@ -747,6 +751,57 @@ impl Instance {
747751
}
748752
}
749753

754+
/// Performs the `memory.init` operation.
755+
///
756+
/// # Errors
757+
///
758+
/// Returns a `Trap` error if the destination range is out of this module's
759+
/// memory's bounds or if the source range is outside the data segment's
760+
/// bounds.
761+
pub(crate) fn memory_init(
762+
&self,
763+
memory_index: MemoryIndex,
764+
data_index: DataIndex,
765+
dst: u32,
766+
src: u32,
767+
len: u32,
768+
source_loc: ir::SourceLoc,
769+
) -> Result<(), Trap> {
770+
// https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-memory-init
771+
772+
let memory = self.get_memory(memory_index);
773+
let passive_data = self.passive_data.borrow();
774+
let data = passive_data
775+
.get(&data_index)
776+
.map_or(&[][..], |data| &**data);
777+
778+
if src
779+
.checked_add(len)
780+
.map_or(true, |n| n as usize > data.len())
781+
|| dst
782+
.checked_add(len)
783+
.map_or(true, |m| m as usize > memory.current_length)
784+
{
785+
return Err(Trap::wasm(source_loc, ir::TrapCode::HeapOutOfBounds));
786+
}
787+
788+
let src_slice = &data[src as usize..(src + len) as usize];
789+
790+
unsafe {
791+
let dst_start = memory.base.add(dst as usize);
792+
let dst_slice = slice::from_raw_parts_mut(dst_start, len as usize);
793+
dst_slice.copy_from_slice(src_slice);
794+
}
795+
796+
Ok(())
797+
}
798+
799+
/// Drop the given data segment, truncating its length to zero.
800+
pub(crate) fn data_drop(&self, data_index: DataIndex) {
801+
let mut passive_data = self.passive_data.borrow_mut();
802+
passive_data.remove(&data_index);
803+
}
804+
750805
/// Get a table by index regardless of whether it is locally-defined or an
751806
/// imported, foreign table.
752807
pub(crate) fn get_table(&self, table_index: TableIndex) -> &Table {
@@ -824,6 +879,8 @@ impl InstanceHandle {
824879

825880
let offsets = VMOffsets::new(mem::size_of::<*const u8>() as u8, &module.local);
826881

882+
let passive_data = RefCell::new(module.passive_data.clone());
883+
827884
let handle = {
828885
let instance = Instance {
829886
refcount: Cell::new(1),
@@ -833,6 +890,7 @@ impl InstanceHandle {
833890
memories,
834891
tables,
835892
passive_elements: Default::default(),
893+
passive_data,
836894
finished_functions,
837895
dbg_jit_registration,
838896
host_state,

0 commit comments

Comments
 (0)