Skip to content
This repository was archived by the owner on Oct 3, 2025. It is now read-only.

Commit b0b4eba

Browse files
feat: Bulk Memory Operations Proposal
Signed-off-by: Henry Gressmann <[email protected]>
1 parent fd91a1c commit b0b4eba

File tree

12 files changed

+223
-102
lines changed

12 files changed

+223
-102
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
# Status
1414

15-
TinyWasm, starting from version `0.3.0`, passes all the WebAssembly 1.0 tests in the [WebAssembly Test Suite](https://github.com/WebAssembly/testsuite). The 2.0 tests are in progress (notably `simd` and `bulk-memory-operations` are not implemented yet). This is enough to run most WebAssembly programs, including TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)).
15+
TinyWasm, starting from version `0.3.0`, passes all the WebAssembly 1.0 tests in the [WebAssembly Test Suite](https://github.com/WebAssembly/testsuite). The 2.0 tests are in progress. This is enough to run most WebAssembly programs, including TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)).
1616

1717
Some APIs to interact with the runtime are not yet exposed, and the existing ones are subject to change, but the core functionality is mostly complete.
1818
Results of the tests can be found [here](https://github.com/explodingcamera/tinywasm/tree/main/crates/tinywasm/tests/generated).
@@ -24,7 +24,7 @@ TinyWasm is not designed for performance, but rather for size and portability. H
2424
- [**Mutable Globals**](https://github.com/WebAssembly/mutable-global/blob/master/proposals/mutable-global/Overview.md) - **Fully implemented**
2525
- [**Multi-value**](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md) - **Fully implemented**
2626
- [**Sign-extension operators**](https://github.com/WebAssembly/spec/blob/master/proposals/sign-extension-ops/Overview.md) - **Fully implemented**
27-
- [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) - **_Partially implemented_**
27+
- [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) - **Fully implemented** (as of version `0.4.0`)
2828
- [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) - **_Partially implemented_**
2929
- [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) - **_Partially implemented_** (not tested yet)
3030
- [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) - **_Partially implemented_** (only 32-bit addressing is supported at the moment, but larger memories can be created)

crates/parser/src/conversion.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ pub(crate) fn convert_module_tables<T: IntoIterator<Item = wasmparser::Result<wa
107107
table_types: T,
108108
) -> Result<Vec<TableType>> {
109109
let table_type = table_types.into_iter().map(|table| convert_module_table(table?)).collect::<Result<Vec<_>>>()?;
110-
111110
Ok(table_type)
112111
}
113112

crates/parser/src/module.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ impl ModuleReader {
112112
if !self.table_types.is_empty() {
113113
return Err(ParseError::DuplicateSection("Table section".into()));
114114
}
115-
116115
debug!("Found table section");
117116
validator.table_section(&reader)?;
118117
self.table_types = conversion::convert_module_tables(reader)?;
@@ -140,6 +139,13 @@ impl ModuleReader {
140139
validator.data_section(&reader)?;
141140
self.data = conversion::convert_module_data_sections(reader)?;
142141
}
142+
DataCountSection { count, range } => {
143+
debug!("Found data count section");
144+
if !self.data.is_empty() {
145+
return Err(ParseError::DuplicateSection("Data count section".into()));
146+
}
147+
validator.data_count_section(count, &range)?;
148+
}
143149
CodeSectionStart { count, range, .. } => {
144150
debug!("Found code section ({} functions)", count);
145151
if !self.code.is_empty() {

crates/tinywasm/src/imports.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ pub struct FuncContext<'a> {
6262
}
6363

6464
impl FuncContext<'_> {
65-
/// Get a mutable reference to the store
66-
pub fn store_mut(&mut self) -> &mut crate::Store {
65+
/// Get a reference to the store
66+
pub fn store(&self) -> &crate::Store {
6767
self.store
6868
}
6969

70-
/// Get a reference to the store
71-
pub fn store(&self) -> &crate::Store {
70+
/// Get a mutable reference to the store
71+
pub fn store_mut(&mut self) -> &mut crate::Store {
7272
self.store
7373
}
7474

@@ -78,9 +78,14 @@ impl FuncContext<'_> {
7878
}
7979

8080
/// Get a reference to an exported memory
81-
pub fn memory(&mut self, name: &str) -> Result<crate::MemoryRef> {
81+
pub fn exported_memory(&mut self, name: &str) -> Result<crate::MemoryRef<'_>> {
8282
self.module().exported_memory(self.store, name)
8383
}
84+
85+
/// Get a reference to an exported memory
86+
pub fn exported_memory_mut(&mut self, name: &str) -> Result<crate::MemoryRefMut<'_>> {
87+
self.module().exported_memory_mut(self.store, name)
88+
}
8489
}
8590

8691
impl Debug for HostFunction {

crates/tinywasm/src/instance.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use tinywasm_types::*;
33

44
use crate::{
55
func::{FromWasmValueTuple, IntoWasmValueTuple},
6-
log, Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, Module, Result, Store,
6+
log, Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut, Module, Result, Store,
77
};
88

99
/// An instanciated WebAssembly module
@@ -152,6 +152,11 @@ impl ModuleInstance {
152152
*self.0.mem_addrs.get(addr as usize).expect("No mem addr for mem, this is a bug")
153153
}
154154

155+
// resolve a data address to the global store address
156+
pub(crate) fn resolve_data_addr(&self, addr: DataAddr) -> MemAddr {
157+
*self.0.data_addrs.get(addr as usize).expect("No data addr for data, this is a bug")
158+
}
159+
155160
// resolve a memory address to the global store address
156161
pub(crate) fn resolve_elem_addr(&self, addr: ElemAddr) -> ElemAddr {
157162
*self.0.elem_addrs.get(addr as usize).expect("No elem addr for elem, this is a bug")
@@ -190,7 +195,7 @@ impl ModuleInstance {
190195
}
191196

192197
/// Get an exported memory by name
193-
pub fn exported_memory(&self, store: &mut Store, name: &str) -> Result<MemoryRef> {
198+
pub fn exported_memory<'a>(&self, store: &'a mut Store, name: &str) -> Result<MemoryRef<'a>> {
194199
let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {}", name)))?;
195200
let ExternVal::Memory(mem_addr) = export else {
196201
return Err(Error::Other(format!("Export is not a memory: {}", name)));
@@ -199,11 +204,28 @@ impl ModuleInstance {
199204
Ok(mem)
200205
}
201206

207+
/// Get an exported memory by name
208+
pub fn exported_memory_mut<'a>(&self, store: &'a mut Store, name: &str) -> Result<MemoryRefMut<'a>> {
209+
let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {}", name)))?;
210+
let ExternVal::Memory(mem_addr) = export else {
211+
return Err(Error::Other(format!("Export is not a memory: {}", name)));
212+
};
213+
let mem = self.memory_mut(store, mem_addr)?;
214+
Ok(mem)
215+
}
216+
202217
/// Get a memory by address
203-
pub fn memory(&self, store: &Store, addr: MemAddr) -> Result<MemoryRef> {
218+
pub fn memory<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result<MemoryRef<'a>> {
219+
let addr = self.resolve_mem_addr(addr);
220+
let mem = store.get_mem(addr as usize)?;
221+
Ok(MemoryRef { instance: mem.borrow() })
222+
}
223+
224+
/// Get a memory by address (mutable)
225+
pub fn memory_mut<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result<MemoryRefMut<'a>> {
204226
let addr = self.resolve_mem_addr(addr);
205227
let mem = store.get_mem(addr as usize)?;
206-
Ok(MemoryRef { instance: mem.clone() })
228+
Ok(MemoryRefMut { instance: mem.borrow_mut() })
207229
}
208230

209231
/// Get the start function of the module

crates/tinywasm/src/reference.rs

Lines changed: 77 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use core::{
2-
cell::{Ref, RefCell},
2+
cell::{Ref, RefCell, RefMut},
33
ffi::CStr,
44
};
55

@@ -15,91 +15,121 @@ use tinywasm_types::WasmValue;
1515
// This module essentially contains the public APIs to interact with the data stored in the store
1616

1717
/// A reference to a memory instance
18-
#[derive(Debug, Clone)]
19-
pub struct MemoryRef {
20-
pub(crate) instance: Rc<RefCell<MemoryInstance>>,
18+
#[derive(Debug)]
19+
pub struct MemoryRef<'a> {
20+
pub(crate) instance: Ref<'a, MemoryInstance>,
2121
}
2222

2323
/// A borrowed reference to a memory instance
2424
#[derive(Debug)]
25-
pub struct BorrowedMemory<'a> {
26-
pub(crate) instance: Ref<'a, MemoryInstance>,
25+
pub struct MemoryRefMut<'a> {
26+
pub(crate) instance: RefMut<'a, MemoryInstance>,
2727
}
2828

29-
impl<'a> BorrowedMemory<'a> {
29+
impl<'a> MemoryRefLoad for MemoryRef<'a> {
3030
/// Load a slice of memory
31-
pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> {
31+
fn load(&self, offset: usize, len: usize) -> Result<&[u8]> {
3232
self.instance.load(offset, 0, len)
3333
}
34+
}
3435

35-
/// Load a C-style string from memory
36-
pub fn load_cstr(&self, offset: usize, len: usize) -> Result<&CStr> {
37-
let bytes = self.load(offset, len)?;
38-
CStr::from_bytes_with_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string()))
36+
impl<'a> MemoryRefLoad for MemoryRefMut<'a> {
37+
/// Load a slice of memory
38+
fn load(&self, offset: usize, len: usize) -> Result<&[u8]> {
39+
self.instance.load(offset, 0, len)
3940
}
41+
}
4042

41-
/// Load a C-style string from memory, stopping at the first nul byte
42-
pub fn load_cstr_until_nul(&self, offset: usize, max_len: usize) -> Result<&CStr> {
43-
let bytes = self.load(offset, max_len)?;
44-
CStr::from_bytes_until_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string()))
43+
impl MemoryRef<'_> {
44+
/// Load a slice of memory
45+
pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> {
46+
self.instance.load(offset, 0, len)
4547
}
46-
}
4748

48-
impl MemoryRef {
49-
/// Borrow the memory instance
50-
///
51-
/// This is useful for when you want to load only a reference to a slice of memory
52-
/// without copying the data. The borrow should be dropped before any other memory
53-
/// operations are performed.
54-
pub fn borrow(&self) -> BorrowedMemory<'_> {
55-
BorrowedMemory { instance: self.instance.borrow() }
49+
/// Load a slice of memory as a vector
50+
pub fn load_vec(&self, offset: usize, len: usize) -> Result<Vec<u8>> {
51+
self.load(offset, len).map(|x| x.to_vec())
5652
}
53+
}
5754

55+
impl MemoryRefMut<'_> {
5856
/// Load a slice of memory
57+
pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> {
58+
self.instance.load(offset, 0, len)
59+
}
60+
61+
/// Load a slice of memory as a vector
5962
pub fn load_vec(&self, offset: usize, len: usize) -> Result<Vec<u8>> {
60-
self.instance.borrow().load(offset, 0, len).map(|x| x.to_vec())
63+
self.load(offset, len).map(|x| x.to_vec())
6164
}
6265

6366
/// Grow the memory by the given number of pages
64-
pub fn grow(&self, delta_pages: i32) -> Option<i32> {
65-
self.instance.borrow_mut().grow(delta_pages)
67+
pub fn grow(&mut self, delta_pages: i32) -> Option<i32> {
68+
self.instance.grow(delta_pages)
6669
}
6770

6871
/// Get the current size of the memory in pages
69-
pub fn page_count(&self) -> usize {
70-
self.instance.borrow().page_count()
72+
pub fn page_count(&mut self) -> usize {
73+
self.instance.page_count()
7174
}
7275

7376
/// Copy a slice of memory to another place in memory
74-
pub fn copy_within(&self, src: usize, dst: usize, len: usize) -> Result<()> {
75-
self.instance.borrow_mut().copy_within(src, dst, len)
77+
pub fn copy_within(&mut self, src: usize, dst: usize, len: usize) -> Result<()> {
78+
self.instance.copy_within(src, dst, len)
7679
}
7780

7881
/// Fill a slice of memory with a value
79-
pub fn fill(&self, offset: usize, len: usize, val: u8) -> Result<()> {
80-
self.instance.borrow_mut().fill(offset, len, val)
82+
pub fn fill(&mut self, offset: usize, len: usize, val: u8) -> Result<()> {
83+
self.instance.fill(offset, len, val)
84+
}
85+
86+
/// Store a slice of memory
87+
pub fn store(&mut self, offset: usize, len: usize, data: &[u8]) -> Result<()> {
88+
self.instance.store(offset, 0, data, len)
89+
}
90+
}
91+
92+
#[doc(hidden)]
93+
pub trait MemoryRefLoad {
94+
fn load(&self, offset: usize, len: usize) -> Result<&[u8]>;
95+
fn load_vec(&self, offset: usize, len: usize) -> Result<Vec<u8>> {
96+
self.load(offset, len).map(|x| x.to_vec())
97+
}
98+
}
99+
100+
/// Convenience methods for loading strings from memory
101+
pub trait MemoryStringExt: MemoryRefLoad {
102+
/// Load a C-style string from memory
103+
fn load_cstr(&self, offset: usize, len: usize) -> Result<&CStr> {
104+
let bytes = self.load(offset, len)?;
105+
CStr::from_bytes_with_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string()))
106+
}
107+
108+
/// Load a C-style string from memory, stopping at the first nul byte
109+
fn load_cstr_until_nul(&self, offset: usize, max_len: usize) -> Result<&CStr> {
110+
let bytes = self.load(offset, max_len)?;
111+
CStr::from_bytes_until_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string()))
81112
}
82113

83114
/// Load a UTF-8 string from memory
84-
pub fn load_string(&self, offset: usize, len: usize) -> Result<String> {
85-
let bytes = self.load_vec(offset, len)?;
86-
String::from_utf8(bytes).map_err(|_| crate::Error::Other("Invalid UTF-8 string".to_string()))
115+
fn load_string(&self, offset: usize, len: usize) -> Result<String> {
116+
let bytes = self.load(offset, len)?;
117+
String::from_utf8(bytes.to_vec()).map_err(|_| crate::Error::Other("Invalid UTF-8 string".to_string()))
87118
}
88119

89120
/// Load a C-style string from memory
90-
pub fn load_cstring(&self, offset: usize, len: usize) -> Result<CString> {
91-
Ok(CString::from(self.borrow().load_cstr(offset, len)?))
121+
fn load_cstring(&self, offset: usize, len: usize) -> Result<CString> {
122+
Ok(CString::from(self.load_cstr(offset, len)?))
92123
}
93124

94125
/// Load a C-style string from memory, stopping at the first nul byte
95-
pub fn load_cstring_until_nul(&self, offset: usize, max_len: usize) -> Result<CString> {
96-
Ok(CString::from(self.borrow().load_cstr_until_nul(offset, max_len)?))
126+
fn load_cstring_until_nul(&self, offset: usize, max_len: usize) -> Result<CString> {
127+
Ok(CString::from(self.load_cstr_until_nul(offset, max_len)?))
97128
}
98129

99130
/// Load a JavaScript-style utf-16 string from memory
100-
pub fn load_js_string(&self, offset: usize, len: usize) -> Result<String> {
101-
let memref = self.borrow();
102-
let bytes = memref.load(offset, len)?;
131+
fn load_js_string(&self, offset: usize, len: usize) -> Result<String> {
132+
let bytes = self.load(offset, len)?;
103133
let mut string = String::new();
104134
for i in 0..(len / 2) {
105135
let c = u16::from_le_bytes([bytes[i * 2], bytes[i * 2 + 1]]);
@@ -109,13 +139,11 @@ impl MemoryRef {
109139
}
110140
Ok(string)
111141
}
112-
113-
/// Store a slice of memory
114-
pub fn store(&self, offset: usize, len: usize, data: &[u8]) -> Result<()> {
115-
self.instance.borrow_mut().store(offset, 0, data, len)
116-
}
117142
}
118143

144+
impl MemoryStringExt for MemoryRef<'_> {}
145+
impl MemoryStringExt for MemoryRefMut<'_> {}
146+
119147
/// A reference to a global instance
120148
#[derive(Debug, Clone)]
121149
pub struct GlobalRef {

crates/tinywasm/src/runtime/interpreter/mod.rs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -418,12 +418,39 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M
418418
mem.fill(dst as usize, size as usize, val as u8)?;
419419
}
420420

421-
// MemoryInit(data_index, mem_index) => {}
422-
// DataDrop(data_index) => {
423-
// // let data_idx = module.resolve_data_addr(*data_index);
424-
// // let data = store.get_data(data_idx as usize)?;
425-
// // data.borrow_mut().drop()?;
426-
// }
421+
MemoryInit(data_index, mem_index) => {
422+
let size = stack.values.pop_t::<i32>()? as usize;
423+
let offset = stack.values.pop_t::<i32>()? as usize;
424+
let dst = stack.values.pop_t::<i32>()? as usize;
425+
426+
let data_idx = module.resolve_data_addr(*data_index);
427+
let Some(ref data) = store.get_data(data_idx as usize)?.data else {
428+
cold();
429+
return Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 }.into());
430+
};
431+
432+
let mem_idx = module.resolve_mem_addr(*mem_index);
433+
let mem = store.get_mem(mem_idx as usize)?;
434+
435+
let data_len = data.len();
436+
if offset + size > data_len {
437+
cold();
438+
return Err(Trap::MemoryOutOfBounds { offset, len: size, max: data_len }.into());
439+
}
440+
441+
let mut mem = mem.borrow_mut();
442+
let data = &data[offset..(offset + size)];
443+
444+
// mem.store checks bounds
445+
mem.store(dst, 0, data, size)?;
446+
}
447+
448+
DataDrop(data_index) => {
449+
let data_idx = module.resolve_data_addr(*data_index);
450+
let data = store.get_data_mut(data_idx as usize)?;
451+
data.drop();
452+
}
453+
427454
I32Store(arg) => mem_store!(i32, arg, stack, store, module),
428455
I64Store(arg) => mem_store!(i64, arg, stack, store, module),
429456
F32Store(arg) => mem_store!(f32, arg, stack, store, module),

0 commit comments

Comments
 (0)