Skip to content

Commit 1252a89

Browse files
feat: memory64 support (#38)
Signed-off-by: Henry Gressmann <[email protected]>
1 parent 569e0b4 commit 1252a89

File tree

11 files changed

+83
-62
lines changed

11 files changed

+83
-62
lines changed

Cargo.lock

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+8-4
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@
1414

1515
- **Tiny**: TinyWasm is designed to be as small as possible without significantly compromising performance or functionality (< 4000 LLOC).
1616
- **Portable**: TinyWasm runs on any platform that Rust can target, including `no_std`, with minimal external dependencies.
17-
- **Safe**: No unsafe code is used in the runtime (`rkyv`, which uses unsafe code, can be used for serialization but is optional).
17+
- **Safe**: No unsafe code is used in the runtime
1818

19-
## Status
19+
## Current Status
2020

21-
TinyWasm passes all WebAssembly MVP tests from the [WebAssembly core testsuite](https://github.com/WebAssembly/testsuite) and is able to run most WebAssembly programs. Additionally, the current 2.0 Draft is mostly supported, with the exception of Fixed-Width SIMD and Memory64/Multiple Memories. See the [Supported Proposals](#supported-proposals) section for more information.
21+
TinyWasm passes all WebAssembly MVP tests from the [WebAssembly core testsuite](https://github.com/WebAssembly/testsuite) and is able to run most WebAssembly programs. Additionally, the current 2.0 WebAssembly is mostly supported, with the exception of the SIMD and Memory64 proposals. See the [Supported Proposals](#supported-proposals) section for more information.
22+
23+
## Safety
24+
25+
Safety wise, TinyWasm doesn't use any unsafe code and is designed to be completly memory-safe. Untrusted WebAssembly code should not be able to crash the runtime or access memory outside of its sandbox, however currently there is no protection against infinite loops or excessive memory usage. Unvalidated Wasm and untrusted, precompilled twasm bytecode is safe to run too but can crash the runtime.
2226

2327
## Supported Proposals
2428

@@ -38,7 +42,7 @@ TinyWasm passes all WebAssembly MVP tests from the [WebAssembly core testsuite](
3842
| [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) | 🟢 | 0.8.0 |
3943
| [**Custom Page Sizes**](https://github.com/WebAssembly/custom-page-sizes/blob/main/proposals/custom-page-sizes/Overview.md) | 🟢 | `next` |
4044
| [**Tail Call**](https://github.com/WebAssembly/tail-call/blob/main/proposals/tail-call/Overview.md) | 🟢 | `next` |
41-
| [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | 🚧 | N/A |
45+
| [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | 🟢 | `next` |
4246
| [**Fixed-Width SIMD**](https://github.com/webassembly/simd) | 🚧 | N/A |
4347

4448
## Usage

crates/tinywasm/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ tinywasm-types={version="0.9.0-alpha.0", path="../types", default-features=false
2020
libm={version="0.2", default-features=false}
2121

2222
[dev-dependencies]
23-
wasm-testsuite={version="0.4.4"}
23+
wasm-testsuite={version="0.5.0"}
2424
indexmap="2.7"
2525
wast={workspace=true}
2626
wat={workspace=true}

crates/tinywasm/src/interpreter/executor.rs

+40-15
Original file line numberDiff line numberDiff line change
@@ -518,16 +518,31 @@ impl<'store, 'stack> Executor<'store, 'stack> {
518518

519519
fn exec_memory_size(&mut self, addr: u32) {
520520
let mem = self.store.get_mem(self.module.resolve_mem_addr(addr));
521-
self.stack.values.push::<i32>(mem.page_count as i32);
521+
522+
match mem.is_64bit() {
523+
true => self.stack.values.push::<i64>(mem.page_count as i64),
524+
false => self.stack.values.push::<i32>(mem.page_count as i32),
525+
}
522526
}
523527
fn exec_memory_grow(&mut self, addr: u32) {
524528
let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(addr));
525-
let prev_size = mem.page_count as i32;
526-
let pages_delta = self.stack.values.pop::<i32>();
527-
self.stack.values.push::<i32>(match mem.grow(pages_delta) {
528-
Some(_) => prev_size,
529-
None => -1,
530-
});
529+
let prev_size = mem.page_count;
530+
531+
let pages_delta = match mem.is_64bit() {
532+
true => self.stack.values.pop::<i64>(),
533+
false => self.stack.values.pop::<i32>() as i64,
534+
};
535+
536+
match (
537+
mem.is_64bit(),
538+
match mem.grow(pages_delta) {
539+
Some(_) => prev_size as i64,
540+
None => -1_i64,
541+
},
542+
) {
543+
(true, size) => self.stack.values.push::<i64>(size),
544+
(false, size) => self.stack.values.push::<i32>(size as i32),
545+
};
531546
}
532547

533548
fn exec_memory_copy(&mut self, from: u32, to: u32) -> Result<()> {
@@ -605,14 +620,13 @@ impl<'store, 'stack> Executor<'store, 'stack> {
605620
dst as usize,
606621
src as usize,
607622
size as usize,
608-
)?;
623+
)
609624
} else {
610625
// copy between two memories
611626
let (table_from, table_to) =
612627
self.store.get_tables_mut(self.module.resolve_table_addr(from), self.module.resolve_table_addr(to))?;
613-
table_to.copy_from_slice(dst as usize, table_from.load(src as usize, size as usize)?)?;
628+
table_to.copy_from_slice(dst as usize, table_from.load(src as usize, size as usize)?)
614629
}
615-
Ok(())
616630
}
617631

618632
fn exec_mem_load<LOAD: MemLoadable<LOAD_SIZE>, const LOAD_SIZE: usize, TARGET: InternalValue>(
@@ -622,11 +636,16 @@ impl<'store, 'stack> Executor<'store, 'stack> {
622636
cast: fn(LOAD) -> TARGET,
623637
) -> ControlFlow<Option<Error>> {
624638
let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr));
625-
let val = self.stack.values.pop::<i32>() as u64;
626-
let Some(Ok(addr)) = offset.checked_add(val).map(TryInto::try_into) else {
639+
640+
let addr = match mem.is_64bit() {
641+
true => self.stack.values.pop::<i64>() as u64,
642+
false => self.stack.values.pop::<i32>() as u32 as u64,
643+
};
644+
645+
let Some(Ok(addr)) = offset.checked_add(addr).map(TryInto::try_into) else {
627646
cold();
628647
return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds {
629-
offset: val as usize,
648+
offset: addr as usize,
630649
len: LOAD_SIZE,
631650
max: 0,
632651
})));
@@ -644,10 +663,16 @@ impl<'store, 'stack> Executor<'store, 'stack> {
644663
let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(mem_addr));
645664
let val = self.stack.values.pop::<T>();
646665
let val = (cast(val)).to_mem_bytes();
647-
let addr = self.stack.values.pop::<i32>() as u64;
666+
667+
let addr = match mem.is_64bit() {
668+
true => self.stack.values.pop::<i64>() as u64,
669+
false => self.stack.values.pop::<i32>() as u32 as u64,
670+
};
671+
648672
if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) {
649673
return ControlFlow::Break(Some(e));
650674
}
675+
651676
ControlFlow::Continue(())
652677
}
653678

@@ -707,7 +732,7 @@ impl<'store, 'stack> Executor<'store, 'stack> {
707732
return Err(Trap::TableOutOfBounds { offset: 0, len: 0, max: 0 }.into());
708733
};
709734

710-
table.init(dst, &items[offset as usize..(offset + size) as usize])
735+
table.init(dst as i64, &items[offset as usize..(offset + size) as usize])
711736
}
712737
fn exec_table_grow(&mut self, table_index: u32) -> Result<()> {
713738
let table = self.store.get_table_mut(self.module.resolve_table_addr(table_index));

crates/tinywasm/src/reference.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ impl MemoryRefMut<'_> {
5454
}
5555

5656
/// Grow the memory by the given number of pages
57-
pub fn grow(&mut self, delta_pages: i32) -> Option<i32> {
57+
pub fn grow(&mut self, delta_pages: i64) -> Option<i64> {
5858
self.0.grow(delta_pages)
5959
}
6060

crates/tinywasm/src/store/memory.rs

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use alloc::vec;
22
use alloc::vec::Vec;
3-
use tinywasm_types::{MemoryType, ModuleInstanceAddr};
3+
use tinywasm_types::{MemoryArch, MemoryType, ModuleInstanceAddr};
44

55
use crate::{Error, Result, cold, log};
66

@@ -28,6 +28,11 @@ impl MemoryInstance {
2828
}
2929
}
3030

31+
#[inline]
32+
pub(crate) fn is_64bit(&self) -> bool {
33+
matches!(self.kind.arch(), MemoryArch::I64)
34+
}
35+
3136
#[inline(always)]
3237
pub(crate) fn len(&self) -> usize {
3338
self.data.len()
@@ -124,15 +129,13 @@ impl MemoryInstance {
124129
}
125130

126131
#[inline]
127-
pub(crate) fn grow(&mut self, pages_delta: i32) -> Option<i32> {
132+
pub(crate) fn grow(&mut self, pages_delta: i64) -> Option<i64> {
128133
let current_pages = self.page_count;
129-
let new_pages = current_pages as i64 + pages_delta as i64;
130-
debug_assert!(new_pages <= i32::MAX as i64, "page count should never be greater than i32::MAX");
134+
let new_pages = current_pages as i64 + pages_delta;
131135

132136
if new_pages < 0 || new_pages as usize > self.max_pages() {
133137
log::debug!("memory.grow failed: new_pages={}, max_pages={}", new_pages, self.max_pages());
134138
log::debug!("{} {}", self.kind.page_count_max(), self.kind.page_size());
135-
136139
return None;
137140
}
138141

@@ -145,7 +148,7 @@ impl MemoryInstance {
145148
self.data.reserve_exact(new_size);
146149
self.data.resize_with(new_size, Default::default);
147150
self.page_count = new_pages as usize;
148-
Some(current_pages as i32)
151+
Some(current_pages as i64)
149152
}
150153
}
151154

@@ -241,14 +244,14 @@ mod memory_instance_tests {
241244
fn test_memory_grow() {
242245
let mut memory = create_test_memory();
243246
let original_pages = memory.page_count;
244-
assert_eq!(memory.grow(1), Some(original_pages as i32));
247+
assert_eq!(memory.grow(1), Some(original_pages as i64));
245248
assert_eq!(memory.page_count, original_pages + 1);
246249
}
247250

248251
#[test]
249252
fn test_memory_grow_out_of_bounds() {
250253
let mut memory = create_test_memory();
251-
assert!(memory.grow(memory.kind.max_size() as i32 + 1).is_none());
254+
assert!(memory.grow(memory.kind.max_size() as i64 + 1).is_none());
252255
}
253256

254257
#[test]

crates/tinywasm/src/store/mod.rs

+14-14
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,6 @@ impl Store {
243243
let mem_count = self.data.memories.len();
244244
let mut mem_addrs = Vec::with_capacity(mem_count);
245245
for (i, mem) in memories.into_iter().enumerate() {
246-
if let MemoryArch::I64 = mem.arch() {
247-
return Err(Error::UnsupportedFeature("64-bit memories".to_string()));
248-
}
249246
self.data.memories.push(MemoryInstance::new(mem, idx));
250247
mem_addrs.push((i + mem_count) as MemAddr);
251248
}
@@ -325,7 +322,7 @@ impl Store {
325322

326323
// this one is active, so we need to initialize it (essentially a `table.init` instruction)
327324
ElementKind::Active { offset, table } => {
328-
let offset = self.eval_i32_const(offset)?;
325+
let offset = self.eval_size_const(offset)?;
329326
let table_addr = table_addrs
330327
.get(table as usize)
331328
.copied()
@@ -373,7 +370,7 @@ impl Store {
373370
return Err(Error::Other(format!("memory {mem_addr} not found for data segment {i}")));
374371
};
375372

376-
let offset = self.eval_i32_const(offset)?;
373+
let offset = self.eval_size_const(offset)?;
377374
let Some(mem) = self.data.memories.get_mut(*mem_addr as usize) else {
378375
return Err(Error::Other(format!("memory {mem_addr} not found for data segment {i}")));
379376
};
@@ -418,15 +415,18 @@ impl Store {
418415
Ok(self.data.funcs.len() as FuncAddr - 1)
419416
}
420417

421-
/// Evaluate a constant expression, only supporting i32 globals and i32.const
422-
pub(crate) fn eval_i32_const(&self, const_instr: tinywasm_types::ConstInstruction) -> Result<i32> {
423-
use tinywasm_types::ConstInstruction::*;
424-
let val = match const_instr {
425-
I32Const(i) => i,
426-
GlobalGet(addr) => self.data.globals[addr as usize].value.get().unwrap_32() as i32,
427-
_ => return Err(Error::Other("expected i32".to_string())),
428-
};
429-
Ok(val)
418+
/// Evaluate a constant expression that's either a i32 or a i64 as a global or a const instruction
419+
pub(crate) fn eval_size_const(&self, const_instr: tinywasm_types::ConstInstruction) -> Result<i64> {
420+
Ok(match const_instr {
421+
ConstInstruction::I32Const(i) => i as i64,
422+
ConstInstruction::I64Const(i) => i,
423+
ConstInstruction::GlobalGet(addr) => match self.data.globals[addr as usize].value.get() {
424+
TinyWasmValue::Value32(i) => i as i64,
425+
TinyWasmValue::Value64(i) => i as i64,
426+
o => return Err(Error::Other(format!("expected i32 or i64, got {o:?}"))),
427+
},
428+
o => return Err(Error::Other(format!("expected i32, got {o:?}"))),
429+
})
430430
}
431431

432432
/// Evaluate a constant expression

crates/tinywasm/src/store/table.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ impl TableInstance {
134134
.expect("error initializing table: function not found. This should have been caught by the validator")
135135
}
136136

137-
pub(crate) fn init(&mut self, offset: i32, init: &[TableElement]) -> Result<()> {
137+
pub(crate) fn init(&mut self, offset: i64, init: &[TableElement]) -> Result<()> {
138138
let offset = offset as usize;
139139
let end = offset.checked_add(init.len()).ok_or({
140140
Error::Trap(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() })

0 commit comments

Comments
 (0)