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

Commit 8adce67

Browse files
docs: string example
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
1 parent 6fd283b commit 8adce67

File tree

11 files changed

+261
-28
lines changed

11 files changed

+261
-28
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ There are a couple of low-hanging fruits on the performance side, but they are n
2525
- [**Mutable Globals**](https://github.com/WebAssembly/mutable-global/blob/master/proposals/mutable-global/Overview.md) - **Fully implemented**
2626
- [**Multi-value**](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md) - **Fully implemented**
2727
- [**Sign-extension operators**](https://github.com/WebAssembly/spec/blob/master/proposals/sign-extension-ops/Overview.md) - **Fully implemented**
28+
- [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) - **_Partially implemented_**
2829
- [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) - **_Partially implemented_**
2930
- [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) - **_Partially implemented_** (not tested yet)
3031
- [**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/tinywasm/src/instance.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,14 +199,14 @@ impl ModuleInstance {
199199
pub fn memory(&self, store: &Store, addr: MemAddr) -> Result<MemoryRef> {
200200
let addr = self.resolve_mem_addr(addr);
201201
let mem = store.get_mem(addr as usize)?;
202-
Ok(MemoryRef { _instance: mem.clone() })
202+
Ok(MemoryRef { instance: mem.clone() })
203203
}
204204

205205
/// Get the start function of the module
206206
///
207207
/// Returns None if the module has no start function
208208
/// If no start function is specified, also checks for a _start function in the exports
209-
/// (which is not part of the spec, but used by llvm)
209+
/// (which is not part of the spec, but used by some compilers)
210210
///
211211
/// See <https://webassembly.github.io/spec/core/syntax/modules.html#start-function>
212212
pub fn start_func(&self, store: &Store) -> Result<Option<FuncHandle>> {

crates/tinywasm/src/reference.rs

Lines changed: 123 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,135 @@
1-
use core::cell::RefCell;
1+
use core::{
2+
cell::{Ref, RefCell},
3+
ffi::CStr,
4+
};
5+
6+
use crate::{GlobalInstance, MemoryInstance, Result};
7+
use alloc::{
8+
ffi::CString,
9+
rc::Rc,
10+
string::{String, ToString},
11+
vec::Vec,
12+
};
13+
use tinywasm_types::WasmValue;
214

3-
use crate::{GlobalInstance, MemoryInstance};
4-
use alloc::rc::Rc;
515
// This module essentially contains the public APIs to interact with the data stored in the store
616

717
/// A reference to a memory instance
818
#[derive(Debug, Clone)]
919
pub struct MemoryRef {
10-
pub(crate) _instance: Rc<RefCell<MemoryInstance>>,
20+
pub(crate) instance: Rc<RefCell<MemoryInstance>>,
21+
}
22+
23+
/// A borrowed reference to a memory instance
24+
#[derive(Debug)]
25+
pub struct BorrowedMemory<'a> {
26+
pub(crate) instance: Ref<'a, MemoryInstance>,
27+
}
28+
29+
impl<'a> BorrowedMemory<'a> {
30+
/// Load a slice of memory
31+
pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> {
32+
self.instance.load(offset, 0, len)
33+
}
34+
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()))
39+
}
40+
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()))
45+
}
46+
}
47+
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() }
56+
}
57+
58+
/// Load a slice of memory
59+
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())
61+
}
62+
63+
/// 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)
66+
}
67+
68+
/// Get the current size of the memory in pages
69+
pub fn page_count(&self) -> usize {
70+
self.instance.borrow().page_count()
71+
}
72+
73+
/// 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)
76+
}
77+
78+
/// 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)
81+
}
82+
83+
/// 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+
Ok(String::from_utf8(bytes).map_err(|_| crate::Error::Other("Invalid UTF-8 string".to_string()))?)
87+
}
88+
89+
/// 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)?))
92+
}
93+
94+
/// 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)?))
97+
}
98+
99+
/// 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)?;
103+
let mut string = String::new();
104+
for i in 0..(len / 2) {
105+
let c = u16::from_le_bytes([bytes[i * 2], bytes[i * 2 + 1]]);
106+
string.push(
107+
char::from_u32(c as u32).ok_or_else(|| crate::Error::Other("Invalid UTF-16 string".to_string()))?,
108+
);
109+
}
110+
Ok(string)
111+
}
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+
}
11117
}
12118

13119
/// A reference to a global instance
14120
#[derive(Debug, Clone)]
15121
pub struct GlobalRef {
16-
pub(crate) _instance: Rc<RefCell<GlobalInstance>>,
122+
pub(crate) instance: Rc<RefCell<GlobalInstance>>,
123+
}
124+
125+
impl GlobalRef {
126+
/// Get the value of the global
127+
pub fn get(&self) -> WasmValue {
128+
self.instance.borrow().get()
129+
}
130+
131+
/// Set the value of the global
132+
pub fn set(&self, val: WasmValue) -> Result<()> {
133+
self.instance.borrow_mut().set(val)
134+
}
17135
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ macro_rules! mem_store {
6363
let val = val as $store_type;
6464
let val = val.to_le_bytes();
6565

66-
mem.borrow_mut().store(($arg.offset + addr) as usize, $arg.align as usize, &val)?;
66+
mem.borrow_mut().store(($arg.offset + addr) as usize, $arg.align as usize, &val, val.len())?;
6767
}};
6868
}
6969

crates/tinywasm/src/store.rs

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,9 @@ impl Store {
306306
})?;
307307

308308
// See comment for active element sections in the function above why we need to do this here
309-
if let Err(Error::Trap(trap)) = mem.borrow_mut().store(offset as usize, 0, &data.data) {
309+
if let Err(Error::Trap(trap)) =
310+
mem.borrow_mut().store(offset as usize, 0, &data.data, data.data.len())
311+
{
310312
return Ok((data_addrs.into_boxed_slice(), Some(trap)));
311313
}
312314

@@ -607,8 +609,8 @@ impl MemoryInstance {
607609
}
608610
}
609611

610-
pub(crate) fn store(&mut self, addr: usize, _align: usize, data: &[u8]) -> Result<()> {
611-
let end = addr.checked_add(data.len()).ok_or_else(|| {
612+
pub(crate) fn store(&mut self, addr: usize, _align: usize, data: &[u8], len: usize) -> Result<()> {
613+
let end = addr.checked_add(len).ok_or_else(|| {
612614
Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len: data.len(), max: self.data.len() })
613615
})?;
614616

@@ -646,9 +648,37 @@ impl MemoryInstance {
646648
self.page_count
647649
}
648650

649-
pub(crate) fn grow(&mut self, delta: i32) -> Option<i32> {
651+
pub(crate) fn fill(&mut self, addr: usize, len: usize, val: u8) -> Result<()> {
652+
let end = addr
653+
.checked_add(len)
654+
.ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }))?;
655+
if end > self.data.len() {
656+
return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }));
657+
}
658+
self.data[addr..end].fill(val);
659+
Ok(())
660+
}
661+
662+
pub(crate) fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Result<()> {
663+
let end = src
664+
.checked_add(len)
665+
.ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() }))?;
666+
if end > self.data.len() || end < src {
667+
return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() }));
668+
}
669+
let end = dst
670+
.checked_add(len)
671+
.ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() }))?;
672+
if end > self.data.len() || end < dst {
673+
return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() }));
674+
}
675+
self.data[dst..end].copy_within(src..end, len);
676+
Ok(())
677+
}
678+
679+
pub(crate) fn grow(&mut self, pages_delta: i32) -> Option<i32> {
650680
let current_pages = self.page_count();
651-
let new_pages = current_pages as i64 + delta as i64;
681+
let new_pages = current_pages as i64 + pages_delta as i64;
652682

653683
if new_pages < 0 || new_pages > MAX_PAGES as i64 {
654684
return None;
@@ -669,7 +699,7 @@ impl MemoryInstance {
669699
self.page_count = new_pages as usize;
670700

671701
log::debug!("memory was {} pages", current_pages);
672-
log::debug!("memory grown by {} pages", delta);
702+
log::debug!("memory grown by {} pages", pages_delta);
673703
log::debug!("memory grown to {} pages", self.page_count);
674704

675705
Some(current_pages.try_into().expect("memory size out of bounds, this should have been caught earlier"))
@@ -690,6 +720,25 @@ impl GlobalInstance {
690720
pub(crate) fn new(ty: GlobalType, value: RawWasmValue, owner: ModuleInstanceAddr) -> Self {
691721
Self { ty, value, _owner: owner }
692722
}
723+
724+
pub(crate) fn get(&self) -> WasmValue {
725+
self.value.attach_type(self.ty.ty)
726+
}
727+
728+
pub(crate) fn set(&mut self, val: WasmValue) -> Result<()> {
729+
if val.val_type() != self.ty.ty {
730+
return Err(Error::Other(format!(
731+
"global type mismatch: expected {:?}, got {:?}",
732+
self.ty.ty,
733+
val.val_type()
734+
)));
735+
}
736+
if !self.ty.mutable {
737+
return Err(Error::Other("global is immutable".to_string()));
738+
}
739+
self.value = val.into();
740+
Ok(())
741+
}
693742
}
694743

695744
/// A WebAssembly Element Instance

examples/rust/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ tinywasm={path="../../crates/tinywasm", features=["parser", "std"]}
1616
name="hello"
1717
path="src/hello.rs"
1818

19+
[[bin]]
20+
name="print"
21+
path="src/print.rs"
22+
1923
[[bin]]
2024
name="tinywasm"
2125
path="src/tinywasm.rs"

examples/rust/build.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env bash
22
cd "$(dirname "$0")"
33

4-
bins=("hello" "tinywasm" "fibonacci")
4+
bins=("hello" "fibonacci" "print" "tinywasm")
55
exclude_wat=("tinywasm")
66
out_dir="./target/wasm32-unknown-unknown/wasm"
77
dest_dir="out"
@@ -10,7 +10,7 @@ dest_dir="out"
1010
mkdir -p "$dest_dir"
1111

1212
for bin in "${bins[@]}"; do
13-
cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin"
13+
RUSTFLAGS="-C target-feature=+reference-types -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin"
1414

1515
cp "$out_dir/$bin.wasm" "$dest_dir/"
1616
wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wasm" -O --intrinsic-lowering -O

examples/rust/src/hello.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
1-
#![no_std]
21
#![no_main]
32

4-
#[cfg(not(test))]
5-
#[panic_handler]
6-
fn panic(_info: &core::panic::PanicInfo) -> ! {
7-
core::arch::wasm32::unreachable()
8-
}
9-
103
#[link(wasm_import_module = "env")]
114
extern "C" {
12-
fn printi32(x: i32);
5+
fn print_utf8(location: i32, len: i32);
6+
}
7+
8+
const ARG: &[u8] = &[0u8; 100];
9+
10+
#[no_mangle]
11+
pub unsafe extern "C" fn arg_ptr() -> i32 {
12+
ARG.as_ptr() as i32
1313
}
1414

1515
#[no_mangle]
16-
pub unsafe extern "C" fn add_and_print(lh: i32, rh: i32) {
17-
printi32(lh + rh);
16+
pub unsafe extern "C" fn arg_size() -> i32 {
17+
ARG.len() as i32
18+
}
19+
20+
#[no_mangle]
21+
pub unsafe extern "C" fn hello(len: i32) {
22+
let arg = core::str::from_utf8(&ARG[0..len as usize]).unwrap();
23+
let res = format!("Hello, {}!", arg).as_bytes().to_vec();
24+
25+
let len = res.len() as i32;
26+
let ptr = res.leak().as_ptr() as i32;
27+
print_utf8(ptr, len);
1828
}

examples/rust/src/print.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#![no_std]
2+
#![no_main]
3+
4+
#[cfg(not(test))]
5+
#[panic_handler]
6+
fn panic(_info: &core::panic::PanicInfo) -> ! {
7+
core::arch::wasm32::unreachable()
8+
}
9+
10+
#[link(wasm_import_module = "env")]
11+
extern "C" {
12+
fn printi32(x: i32);
13+
}
14+
15+
#[no_mangle]
16+
pub unsafe extern "C" fn add_and_print(lh: i32, rh: i32) {
17+
printi32(lh + rh);
18+
}

examples/rust/src/tinywasm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub extern "C" fn hello() {
1212
}
1313

1414
fn run() -> tinywasm::Result<()> {
15-
let module = tinywasm::Module::parse_bytes(include_bytes!("../out/hello.wasm"))?;
15+
let module = tinywasm::Module::parse_bytes(include_bytes!("../out/print.wasm"))?;
1616
let mut store = tinywasm::Store::default();
1717
let mut imports = tinywasm::Imports::new();
1818

0 commit comments

Comments
 (0)