Skip to content

Commit 455ffd4

Browse files
feat: add shared memories
Signed-off-by: Henry <mail@henrygressmann.de>
1 parent 49d24c2 commit 455ffd4

19 files changed

Lines changed: 1096 additions & 257 deletions

File tree

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ["cfg(rust_analyzer)"] }
4747

4848
[dev-dependencies]
4949
anyhow.workspace = true
50-
tinywasm = { path = "crates/tinywasm", features = ["state"] }
50+
tinywasm = { path = "crates/tinywasm", features = ["send", "state"] }
5151
tinywasm-wasi.workspace = true
5252
wat.workspace = true
5353

‎crates/cli/src/wast_runner.rs‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use std::time::Duration;
88
use anyhow::{Context, Result, anyhow, bail};
99
use log::{debug, error};
1010
use tinywasm::types::{MemoryType, RefType, RefValue, TableType, WasmType, WasmValue};
11-
use tinywasm::{ExecProgress, Global, HostFunction, Imports, Memory, Module, ModuleInstance, Store, Table};
11+
use tinywasm::{
12+
ExecProgress, Global, HostFunction, Imports, Memory, MemoryShared, Module, ModuleInstance, Store, Table,
13+
};
1214
use wast::QuoteWat;
1315
use wast::core::{AbstractHeapType, NanPattern};
1416

@@ -17,7 +19,7 @@ const TEST_MAX_SUSPENSIONS: u32 = 1000;
1719
// Older suites classify encodings that wasmparser accepts with the latest feature set as malformed.
1820
const ACCEPTED_MALFORMED_MESSAGES: &[&str] =
1921
&["integer representation too long", "zero byte expected", "zero flag expected"];
20-
const ACCEPTED_INVALID_MESSAGES: &[&str] = &["multiple memories"];
22+
const ACCEPTED_INVALID_MESSAGES: &[&str] = &["multiple memories", "multiple tables"];
2123

2224
macro_rules! float_value {
2325
($pattern:expr, $float:ty, $variant:ident) => {
@@ -172,6 +174,8 @@ impl WastRunner {
172174
let table64 = Table::try_new(store, TableType::new64(RefType::FUNCREF, 10, Some(20)), RefValue::Null.into())?;
173175
let memory =
174176
Memory::try_new(store, MemoryType::default().with_page_count_initial(1).with_page_count_max(Some(2)))?;
177+
let shared_memory =
178+
MemoryShared::try_new(MemoryType::default().with_page_count_initial(1).with_page_count_max(Some(2)))?;
175179
let global_i32 =
176180
Global::try_new(store, tinywasm::types::GlobalType::new(WasmType::I32, false), WasmValue::I32(666))?;
177181
let global_i64 =
@@ -183,6 +187,7 @@ impl WastRunner {
183187

184188
imports
185189
.define("spectest", "memory", memory)
190+
.define("spectest", "shared_memory", shared_memory)
186191
.define("spectest", "table", table)
187192
.define("spectest", "table64", table64)
188193
.define("spectest", "global_i32", global_i32)

‎crates/parser/src/conversion.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ pub(crate) fn convert_module_memory(memory: wasmparser::MemoryType) -> MemoryTyp
109109
memory.maximum,
110110
memory.page_size_log2.map(|x| 1 << x),
111111
)
112+
.with_shared(memory.shared)
112113
}
113114

114115
pub(crate) fn convert_module_globals(

‎crates/tinywasm/src/func/context.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ impl FuncContext<'_> {
3030
self.module().memory(name)
3131
}
3232

33+
/// Get a shared memory export.
34+
#[cfg(feature = "std")]
35+
pub fn memory_shared(&self, name: &str) -> Result<crate::MemoryShared> {
36+
self.module().memory_shared(name)
37+
}
38+
3339
/// Get any exported extern value by name.
3440
pub fn extern_item(&self, name: &str) -> Result<crate::ExternItem> {
3541
self.module().extern_item(name)

‎crates/tinywasm/src/imports.rs‎

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use alloc::collections::BTreeMap;
22
use alloc::string::{String, ToString};
33
use alloc::vec::Vec;
44

5+
#[cfg(feature = "std")]
6+
use crate::MemoryShared;
57
use crate::{Function, Global, HostFunction, LinkingError, Memory, Result, Table, Tag};
68
use tinywasm_types::*;
79

@@ -16,6 +18,9 @@ pub enum Extern {
1618
Table(Table),
1719
/// A memory instance.
1820
Memory(Memory),
21+
/// A shared memory, importable into independent stores.
22+
#[cfg(feature = "std")]
23+
MemoryShared(MemoryShared),
1924
/// A function import.
2025
Function(Function),
2126
/// A reusable host function definition.
@@ -45,6 +50,13 @@ impl_conv! {
4550
Tag => Tag,
4651
}
4752

53+
#[cfg(feature = "std")]
54+
impl From<MemoryShared> for Extern {
55+
fn from(value: MemoryShared) -> Self {
56+
Self::MemoryShared(value)
57+
}
58+
}
59+
4860
/// Imports for a module instance
4961
///
5062
/// This is used to link a module instance to its imports
@@ -188,6 +200,8 @@ impl Imports {
188200
crate::ExternItem::Func(value) => Extern::Function(value),
189201
crate::ExternItem::Global(value) => Extern::Global(value),
190202
crate::ExternItem::Memory(value) => Extern::Memory(value),
203+
#[cfg(feature = "std")]
204+
crate::ExternItem::MemoryShared(value) => Extern::MemoryShared(value),
191205
crate::ExternItem::Table(value) => Extern::Table(value),
192206
crate::ExternItem::Tag(value) => Extern::Tag(value),
193207
})
@@ -228,6 +242,7 @@ impl ResolvedImports {
228242
real_size: usize,
229243
) -> Result<()> {
230244
Self::compare_types(import, &expected.arch(), &actual.arch())?;
245+
Self::compare_types(import, &expected.shared(), &actual.shared())?;
231246

232247
if actual.page_count_initial() > expected.page_count_initial() && actual.page_count_initial() > real_size as u64
233248
{
@@ -286,6 +301,16 @@ impl ResolvedImports {
286301
memory.0.validate_store(store)?;
287302
ExternVal::Memory(memory.0.addr)
288303
}
304+
#[cfg(feature = "std")]
305+
Extern::MemoryShared(memory) => {
306+
let index = MemAddr::try_from(store.state.shared_memories.len())
307+
.map_err(|_| crate::Error::UnsupportedFeature("too many shared memories"))?;
308+
if index >= crate::store::SHARED_MEM_BIT - 1 {
309+
return Err(crate::Error::UnsupportedFeature("too many shared memories"));
310+
}
311+
store.state.shared_memories.push(memory.clone());
312+
ExternVal::Memory(index | crate::store::SHARED_MEM_BIT)
313+
}
289314
Extern::Function(func) => {
290315
func.item.validate_store(store)?;
291316
ExternVal::Func(func.addr())
@@ -339,8 +364,21 @@ impl ResolvedImports {
339364
imports.tables.push(table_addr);
340365
}
341366
(ExternVal::Memory(memory_addr), ImportKind::Memory(ty)) => {
342-
let mem = store.state.get_mem(memory_addr);
343-
Self::compare_memory_types(import, &mem.kind, ty, mem.page_count)?;
367+
#[cfg(feature = "std")]
368+
let (kind, pages) = if memory_addr & crate::store::SHARED_MEM_BIT != 0 {
369+
let memory =
370+
&store.state.shared_memories[(memory_addr & !crate::store::SHARED_MEM_BIT) as usize];
371+
(memory.ty(), memory.page_count())
372+
} else {
373+
let memory = store.state.get_mem(memory_addr);
374+
(memory.kind, memory.page_count)
375+
};
376+
#[cfg(not(feature = "std"))]
377+
let (kind, pages) = {
378+
let memory = store.state.get_mem(memory_addr);
379+
(memory.kind, memory.page_count)
380+
};
381+
Self::compare_memory_types(import, &kind, ty, pages)?;
344382
imports.memories.push(memory_addr);
345383
}
346384
(ExternVal::Func(func_addr), ImportKind::Function(ty)) => {

‎crates/tinywasm/src/instance.rs‎

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@ use crate::store::MemoryInstance;
99
use crate::{
1010
Error, Function, FunctionTyped, Global, Imports, Memory, Result, Store, StoreItem, Table, Tag, Trap, WasmValue,
1111
};
12+
#[cfg(feature = "std")]
13+
use crate::{MemoryShared, store::SHARED_MEM_BIT};
1214

1315
/// A typed view over an exported extern value.
1416
pub enum ExternItem {
1517
/// Exported function handle.
1618
Func(Function),
1719
/// Exported memory reference.
1820
Memory(Memory),
21+
/// Exported shared memory.
22+
#[cfg(feature = "std")]
23+
MemoryShared(MemoryShared),
1924
/// Exported table reference.
2025
Table(Table),
2126
/// Exported global reference.
@@ -57,6 +62,8 @@ struct ModuleInstanceInner {
5762
func_addrs: Box<[FuncAddr]>,
5863
table_addrs: Box<[TableAddr]>,
5964
mem_addrs: Box<[MemAddr]>,
65+
#[cfg(feature = "std")]
66+
shared_backings: Box<[(MemAddr, MemoryShared)]>,
6067
global_addrs: Box<[GlobalAddr]>,
6168
tag_addrs: Box<[TagAddr]>,
6269
elem_addrs: Box<[ElemAddr]>,
@@ -187,6 +194,19 @@ impl ModuleInstance {
187194
}
188195

189196
fn instantiate_inner(store: &mut Store, module: &Module, imports: &[crate::Extern]) -> Result<Self> {
197+
if module.memory_types.iter().any(|ty| ty.shared() && ty.page_count_max_declared().is_none())
198+
|| module.imports.iter().any(|import| {
199+
matches!(import.kind, ImportKind::Memory(ty) if ty.shared() && ty.page_count_max_declared().is_none())
200+
})
201+
{
202+
return Err(Error::UnsupportedFeature("shared memory requires a maximum"));
203+
}
204+
#[cfg(not(feature = "std"))]
205+
if module.memory_types.iter().any(MemoryType::shared)
206+
|| module.imports.iter().any(|import| matches!(import.kind, ImportKind::Memory(ty) if ty.shared()))
207+
{
208+
return Err(Error::UnsupportedFeature("shared memory requires std"));
209+
}
190210
let type_addrs = store.register_module_types(&module.types);
191211
let id = store.next_module_instance_id();
192212
let mut addrs = crate::imports::ResolvedImports::new(store, module, &type_addrs, imports)?;
@@ -205,13 +225,23 @@ impl ModuleInstance {
205225
let elem_addrs = store.alloc_elements(&addrs.funcs, &addrs.globals, &module.elements, &type_addrs)?;
206226
let data_addrs = store.alloc_data(&module.data);
207227

228+
#[cfg(feature = "std")]
229+
let shared_backings = addrs
230+
.memories
231+
.iter()
232+
.filter(|&&addr| addr & SHARED_MEM_BIT != 0)
233+
.map(|&addr| (addr, store.state.shared_memories[(addr & !SHARED_MEM_BIT) as usize].clone()))
234+
.collect();
235+
208236
let instance = ModuleInstanceInner {
209237
store_id: store.id(),
210238
id,
211239
type_addrs,
212240
func_addrs: addrs.funcs.into_boxed_slice(),
213241
table_addrs: addrs.tables.into_boxed_slice(),
214242
mem_addrs: addrs.memories.into_boxed_slice(),
243+
#[cfg(feature = "std")]
244+
shared_backings,
215245
global_addrs: addrs.globals.into_boxed_slice(),
216246
tag_addrs: addrs.tags.into_boxed_slice(),
217247
elem_addrs,
@@ -295,7 +325,17 @@ impl ModuleInstance {
295325
ExternItem::Table(Table(StoreItem::new(self.0.store_id, self.resolve_table_addr(export.index))))
296326
}
297327
ExternalKind::Memory => {
298-
ExternItem::Memory(Memory(StoreItem::new(self.0.store_id, self.resolve_mem_addr(export.index))))
328+
let addr = self.resolve_mem_addr(export.index);
329+
#[cfg(feature = "std")]
330+
if addr & SHARED_MEM_BIT != 0 {
331+
ExternItem::MemoryShared(
332+
self.shared_backing(addr).expect("exported shared memory has a backing"),
333+
)
334+
} else {
335+
ExternItem::Memory(Memory(StoreItem::new(self.0.store_id, addr)))
336+
}
337+
#[cfg(not(feature = "std"))]
338+
ExternItem::Memory(Memory(StoreItem::new(self.0.store_id, addr)))
299339
}
300340
ExternalKind::Global => {
301341
ExternItem::Global(Global(StoreItem::new(self.0.store_id, self.resolve_global_addr(export.index))))
@@ -352,7 +392,13 @@ impl ModuleInstance {
352392
ExternVal::Func(addr) => {
353393
Ok(ExternItem::Func(Function { item: StoreItem::new(self.0.store_id, addr), module_id: self.id() }))
354394
}
355-
ExternVal::Memory(addr) => Ok(ExternItem::Memory(Memory(StoreItem::new(self.0.store_id, addr)))),
395+
ExternVal::Memory(addr) => {
396+
#[cfg(feature = "std")]
397+
if addr & SHARED_MEM_BIT != 0 {
398+
return Ok(ExternItem::MemoryShared(self.shared_backing(addr)?));
399+
}
400+
Ok(ExternItem::Memory(Memory(StoreItem::new(self.0.store_id, addr))))
401+
}
356402
ExternVal::Table(addr) => Ok(ExternItem::Table(Table(StoreItem::new(self.0.store_id, addr)))),
357403
ExternVal::Global(addr) => Ok(ExternItem::Global(Global(StoreItem::new(self.0.store_id, addr)))),
358404
ExternVal::Tag(addr) => Ok(ExternItem::Tag(Tag(StoreItem::new(self.0.store_id, addr)))),
@@ -482,11 +528,32 @@ impl ModuleInstance {
482528
/// Get a memory export by name.
483529
pub fn memory(&self, name: &str) -> Result<Memory> {
484530
match self.require_export(name)? {
485-
ExternVal::Memory(mem_addr) => Ok(Memory(StoreItem::new(self.0.store_id, mem_addr))),
531+
ExternVal::Memory(mem_addr) if mem_addr & crate::store::SHARED_MEM_BIT == 0 => {
532+
Ok(Memory(StoreItem::new(self.0.store_id, mem_addr)))
533+
}
486534
_ => cold!(Err(Error::Other(format!("Export is not a memory: {name}")))),
487535
}
488536
}
489537

538+
#[cfg(feature = "std")]
539+
fn shared_backing(&self, addr: MemAddr) -> Result<MemoryShared> {
540+
self.0
541+
.shared_backings
542+
.iter()
543+
.find(|(index, _)| *index == addr)
544+
.map(|(_, backing)| backing.clone())
545+
.ok_or_else(|| Error::Other("invalid shared memory address".into()))
546+
}
547+
548+
/// Returns a shared memory export by name.
549+
#[cfg(feature = "std")]
550+
pub fn memory_shared(&self, name: &str) -> Result<MemoryShared> {
551+
match self.require_export(name)? {
552+
ExternVal::Memory(addr) if addr & SHARED_MEM_BIT != 0 => self.shared_backing(addr),
553+
_ => Err(Error::Other(format!("Export is not a shared memory: {name}"))),
554+
}
555+
}
556+
490557
/// Get a memory by its module-local index.
491558
///
492559
/// This exposes an internal module-owned memory directly and bypasses the
@@ -496,7 +563,11 @@ impl ModuleInstance {
496563
#[cfg_attr(docsrs, doc(cfg(feature = "guest-debug")))]
497564
#[cfg(feature = "guest-debug")]
498565
pub fn memory_by_index(&self, memory_index: MemAddr) -> Result<Memory> {
499-
Ok(Memory(StoreItem::new(self.0.store_id, Self::index_addr(&self.0.mem_addrs, memory_index, "memory")?)))
566+
let addr = Self::index_addr(&self.0.mem_addrs, memory_index, "memory")?;
567+
if addr & crate::store::SHARED_MEM_BIT != 0 {
568+
return Err(Error::UnsupportedFeature("use shared memory access for this index"));
569+
}
570+
Ok(Memory(StoreItem::new(self.0.store_id, addr)))
500571
}
501572

502573
/// Get a table export by name.

0 commit comments

Comments
 (0)