summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHenry Gressmann <mail@henrygressmann.de>2024-01-26 18:03:59 +0100
committerHenry Gressmann <mail@henrygressmann.de>2024-01-26 18:03:59 +0100
commit8adce672578683dcc7ab62e5492e0f5dbc07a0b4 (patch)
treeeda57b01f206faf5bcfb7ee70c24fd7be8546e66
parent6fd283b06c4912f5d3408b2072cbc3c4e195a236 (diff)
docs: string example
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
-rw-r--r--README.md1
-rw-r--r--crates/tinywasm/src/instance.rs4
-rw-r--r--crates/tinywasm/src/reference.rs128
-rw-r--r--crates/tinywasm/src/runtime/interpreter/macros.rs2
-rw-r--r--crates/tinywasm/src/store.rs61
-rw-r--r--examples/rust/Cargo.toml4
-rwxr-xr-xexamples/rust/build.sh4
-rw-r--r--examples/rust/src/hello.rs30
-rw-r--r--examples/rust/src/print.rs18
-rw-r--r--examples/rust/src/tinywasm.rs2
-rw-r--r--examples/wasm-rust.rs35
11 files changed, 261 insertions, 28 deletions
diff --git a/README.md b/README.md
index 5055889..57728a4 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@ There are a couple of low-hanging fruits on the performance side, but they are n
- [**Mutable Globals**](https://github.com/WebAssembly/mutable-global/blob/master/proposals/mutable-global/Overview.md) - **Fully implemented**
- [**Multi-value**](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md) - **Fully implemented**
- [**Sign-extension operators**](https://github.com/WebAssembly/spec/blob/master/proposals/sign-extension-ops/Overview.md) - **Fully implemented**
+- [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) - **_Partially implemented_**
- [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) - **_Partially implemented_**
- [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) - **_Partially implemented_** (not tested yet)
- [**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)
diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs
index 5346ac3..35b54d6 100644
--- a/crates/tinywasm/src/instance.rs
+++ b/crates/tinywasm/src/instance.rs
@@ -199,14 +199,14 @@ impl ModuleInstance {
pub fn memory(&self, store: &Store, addr: MemAddr) -> Result<MemoryRef> {
let addr = self.resolve_mem_addr(addr);
let mem = store.get_mem(addr as usize)?;
- Ok(MemoryRef { _instance: mem.clone() })
+ Ok(MemoryRef { instance: mem.clone() })
}
/// Get the start function of the module
///
/// Returns None if the module has no start function
/// If no start function is specified, also checks for a _start function in the exports
- /// (which is not part of the spec, but used by llvm)
+ /// (which is not part of the spec, but used by some compilers)
///
/// See <https://webassembly.github.io/spec/core/syntax/modules.html#start-function>
pub fn start_func(&self, store: &Store) -> Result<Option<FuncHandle>> {
diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs
index bc02338..fdaae18 100644
--- a/crates/tinywasm/src/reference.rs
+++ b/crates/tinywasm/src/reference.rs
@@ -1,17 +1,135 @@
-use core::cell::RefCell;
+use core::{
+ cell::{Ref, RefCell},
+ ffi::CStr,
+};
+
+use crate::{GlobalInstance, MemoryInstance, Result};
+use alloc::{
+ ffi::CString,
+ rc::Rc,
+ string::{String, ToString},
+ vec::Vec,
+};
+use tinywasm_types::WasmValue;
-use crate::{GlobalInstance, MemoryInstance};
-use alloc::rc::Rc;
// This module essentially contains the public APIs to interact with the data stored in the store
/// A reference to a memory instance
#[derive(Debug, Clone)]
pub struct MemoryRef {
- pub(crate) _instance: Rc<RefCell<MemoryInstance>>,
+ pub(crate) instance: Rc<RefCell<MemoryInstance>>,
+}
+
+/// A borrowed reference to a memory instance
+#[derive(Debug)]
+pub struct BorrowedMemory<'a> {
+ pub(crate) instance: Ref<'a, MemoryInstance>,
+}
+
+impl<'a> BorrowedMemory<'a> {
+ /// Load a slice of memory
+ pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> {
+ self.instance.load(offset, 0, len)
+ }
+
+ /// Load a C-style string from memory
+ pub fn load_cstr(&self, offset: usize, len: usize) -> Result<&CStr> {
+ let bytes = self.load(offset, len)?;
+ CStr::from_bytes_with_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string()))
+ }
+
+ /// Load a C-style string from memory, stopping at the first nul byte
+ pub fn load_cstr_until_nul(&self, offset: usize, max_len: usize) -> Result<&CStr> {
+ let bytes = self.load(offset, max_len)?;
+ CStr::from_bytes_until_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string()))
+ }
+}
+
+impl MemoryRef {
+ /// Borrow the memory instance
+ ///
+ /// This is useful for when you want to load only a reference to a slice of memory
+ /// without copying the data. The borrow should be dropped before any other memory
+ /// operations are performed.
+ pub fn borrow(&self) -> BorrowedMemory<'_> {
+ BorrowedMemory { instance: self.instance.borrow() }
+ }
+
+ /// Load a slice of memory
+ pub fn load_vec(&self, offset: usize, len: usize) -> Result<Vec<u8>> {
+ self.instance.borrow().load(offset, 0, len).map(|x| x.to_vec())
+ }
+
+ /// Grow the memory by the given number of pages
+ pub fn grow(&self, delta_pages: i32) -> Option<i32> {
+ self.instance.borrow_mut().grow(delta_pages)
+ }
+
+ /// Get the current size of the memory in pages
+ pub fn page_count(&self) -> usize {
+ self.instance.borrow().page_count()
+ }
+
+ /// Copy a slice of memory to another place in memory
+ pub fn copy_within(&self, src: usize, dst: usize, len: usize) -> Result<()> {
+ self.instance.borrow_mut().copy_within(src, dst, len)
+ }
+
+ /// Fill a slice of memory with a value
+ pub fn fill(&self, offset: usize, len: usize, val: u8) -> Result<()> {
+ self.instance.borrow_mut().fill(offset, len, val)
+ }
+
+ /// Load a UTF-8 string from memory
+ pub fn load_string(&self, offset: usize, len: usize) -> Result<String> {
+ let bytes = self.load_vec(offset, len)?;
+ Ok(String::from_utf8(bytes).map_err(|_| crate::Error::Other("Invalid UTF-8 string".to_string()))?)
+ }
+
+ /// Load a C-style string from memory
+ pub fn load_cstring(&self, offset: usize, len: usize) -> Result<CString> {
+ Ok(CString::from(self.borrow().load_cstr(offset, len)?))
+ }
+
+ /// Load a C-style string from memory, stopping at the first nul byte
+ pub fn load_cstring_until_nul(&self, offset: usize, max_len: usize) -> Result<CString> {
+ Ok(CString::from(self.borrow().load_cstr_until_nul(offset, max_len)?))
+ }
+
+ /// Load a JavaScript-style utf-16 string from memory
+ pub fn load_js_string(&self, offset: usize, len: usize) -> Result<String> {
+ let memref = self.borrow();
+ let bytes = memref.load(offset, len)?;
+ let mut string = String::new();
+ for i in 0..(len / 2) {
+ let c = u16::from_le_bytes([bytes[i * 2], bytes[i * 2 + 1]]);
+ string.push(
+ char::from_u32(c as u32).ok_or_else(|| crate::Error::Other("Invalid UTF-16 string".to_string()))?,
+ );
+ }
+ Ok(string)
+ }
+
+ /// Store a slice of memory
+ pub fn store(&self, offset: usize, len: usize, data: &[u8]) -> Result<()> {
+ self.instance.borrow_mut().store(offset, 0, data, len)
+ }
}
/// A reference to a global instance
#[derive(Debug, Clone)]
pub struct GlobalRef {
- pub(crate) _instance: Rc<RefCell<GlobalInstance>>,
+ pub(crate) instance: Rc<RefCell<GlobalInstance>>,
+}
+
+impl GlobalRef {
+ /// Get the value of the global
+ pub fn get(&self) -> WasmValue {
+ self.instance.borrow().get()
+ }
+
+ /// Set the value of the global
+ pub fn set(&self, val: WasmValue) -> Result<()> {
+ self.instance.borrow_mut().set(val)
+ }
}
diff --git a/crates/tinywasm/src/runtime/interpreter/macros.rs b/crates/tinywasm/src/runtime/interpreter/macros.rs
index 909acb3..cc65812 100644
--- a/crates/tinywasm/src/runtime/interpreter/macros.rs
+++ b/crates/tinywasm/src/runtime/interpreter/macros.rs
@@ -63,7 +63,7 @@ macro_rules! mem_store {
let val = val as $store_type;
let val = val.to_le_bytes();
- mem.borrow_mut().store(($arg.offset + addr) as usize, $arg.align as usize, &val)?;
+ mem.borrow_mut().store(($arg.offset + addr) as usize, $arg.align as usize, &val, val.len())?;
}};
}
diff --git a/crates/tinywasm/src/store.rs b/crates/tinywasm/src/store.rs
index d281b5c..1c0d260 100644
--- a/crates/tinywasm/src/store.rs
+++ b/crates/tinywasm/src/store.rs
@@ -306,7 +306,9 @@ impl Store {
})?;
// See comment for active element sections in the function above why we need to do this here
- if let Err(Error::Trap(trap)) = mem.borrow_mut().store(offset as usize, 0, &data.data) {
+ if let Err(Error::Trap(trap)) =
+ mem.borrow_mut().store(offset as usize, 0, &data.data, data.data.len())
+ {
return Ok((data_addrs.into_boxed_slice(), Some(trap)));
}
@@ -607,8 +609,8 @@ impl MemoryInstance {
}
}
- pub(crate) fn store(&mut self, addr: usize, _align: usize, data: &[u8]) -> Result<()> {
- let end = addr.checked_add(data.len()).ok_or_else(|| {
+ pub(crate) fn store(&mut self, addr: usize, _align: usize, data: &[u8], len: usize) -> Result<()> {
+ let end = addr.checked_add(len).ok_or_else(|| {
Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len: data.len(), max: self.data.len() })
})?;
@@ -646,9 +648,37 @@ impl MemoryInstance {
self.page_count
}
- pub(crate) fn grow(&mut self, delta: i32) -> Option<i32> {
+ pub(crate) fn fill(&mut self, addr: usize, len: usize, val: u8) -> Result<()> {
+ let end = addr
+ .checked_add(len)
+ .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }))?;
+ if end > self.data.len() {
+ return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }));
+ }
+ self.data[addr..end].fill(val);
+ Ok(())
+ }
+
+ pub(crate) fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Result<()> {
+ let end = src
+ .checked_add(len)
+ .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() }))?;
+ if end > self.data.len() || end < src {
+ return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() }));
+ }
+ let end = dst
+ .checked_add(len)
+ .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() }))?;
+ if end > self.data.len() || end < dst {
+ return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() }));
+ }
+ self.data[dst..end].copy_within(src..end, len);
+ Ok(())
+ }
+
+ pub(crate) fn grow(&mut self, pages_delta: i32) -> Option<i32> {
let current_pages = self.page_count();
- let new_pages = current_pages as i64 + delta as i64;
+ let new_pages = current_pages as i64 + pages_delta as i64;
if new_pages < 0 || new_pages > MAX_PAGES as i64 {
return None;
@@ -669,7 +699,7 @@ impl MemoryInstance {
self.page_count = new_pages as usize;
log::debug!("memory was {} pages", current_pages);
- log::debug!("memory grown by {} pages", delta);
+ log::debug!("memory grown by {} pages", pages_delta);
log::debug!("memory grown to {} pages", self.page_count);
Some(current_pages.try_into().expect("memory size out of bounds, this should have been caught earlier"))
@@ -690,6 +720,25 @@ impl GlobalInstance {
pub(crate) fn new(ty: GlobalType, value: RawWasmValue, owner: ModuleInstanceAddr) -> Self {
Self { ty, value, _owner: owner }
}
+
+ pub(crate) fn get(&self) -> WasmValue {
+ self.value.attach_type(self.ty.ty)
+ }
+
+ pub(crate) fn set(&mut self, val: WasmValue) -> Result<()> {
+ if val.val_type() != self.ty.ty {
+ return Err(Error::Other(format!(
+ "global type mismatch: expected {:?}, got {:?}",
+ self.ty.ty,
+ val.val_type()
+ )));
+ }
+ if !self.ty.mutable {
+ return Err(Error::Other("global is immutable".to_string()));
+ }
+ self.value = val.into();
+ Ok(())
+ }
}
/// A WebAssembly Element Instance
diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml
index d557392..837f7f3 100644
--- a/examples/rust/Cargo.toml
+++ b/examples/rust/Cargo.toml
@@ -17,6 +17,10 @@ name="hello"
path="src/hello.rs"
[[bin]]
+name="print"
+path="src/print.rs"
+
+[[bin]]
name="tinywasm"
path="src/tinywasm.rs"
diff --git a/examples/rust/build.sh b/examples/rust/build.sh
index a8c587a..a13357d 100755
--- a/examples/rust/build.sh
+++ b/examples/rust/build.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
cd "$(dirname "$0")"
-bins=("hello" "tinywasm" "fibonacci")
+bins=("hello" "fibonacci" "print" "tinywasm")
exclude_wat=("tinywasm")
out_dir="./target/wasm32-unknown-unknown/wasm"
dest_dir="out"
@@ -10,7 +10,7 @@ dest_dir="out"
mkdir -p "$dest_dir"
for bin in "${bins[@]}"; do
- cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin"
+ RUSTFLAGS="-C target-feature=+reference-types -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin"
cp "$out_dir/$bin.wasm" "$dest_dir/"
wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wasm" -O --intrinsic-lowering -O
diff --git a/examples/rust/src/hello.rs b/examples/rust/src/hello.rs
index 34f3c7f..6bb6d97 100644
--- a/examples/rust/src/hello.rs
+++ b/examples/rust/src/hello.rs
@@ -1,18 +1,28 @@
-#![no_std]
#![no_main]
-#[cfg(not(test))]
-#[panic_handler]
-fn panic(_info: &core::panic::PanicInfo) -> ! {
- core::arch::wasm32::unreachable()
-}
-
#[link(wasm_import_module = "env")]
extern "C" {
- fn printi32(x: i32);
+ fn print_utf8(location: i32, len: i32);
+}
+
+const ARG: &[u8] = &[0u8; 100];
+
+#[no_mangle]
+pub unsafe extern "C" fn arg_ptr() -> i32 {
+ ARG.as_ptr() as i32
}
#[no_mangle]
-pub unsafe extern "C" fn add_and_print(lh: i32, rh: i32) {
- printi32(lh + rh);
+pub unsafe extern "C" fn arg_size() -> i32 {
+ ARG.len() as i32
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn hello(len: i32) {
+ let arg = core::str::from_utf8(&ARG[0..len as usize]).unwrap();
+ let res = format!("Hello, {}!", arg).as_bytes().to_vec();
+
+ let len = res.len() as i32;
+ let ptr = res.leak().as_ptr() as i32;
+ print_utf8(ptr, len);
}
diff --git a/examples/rust/src/print.rs b/examples/rust/src/print.rs
new file mode 100644
index 0000000..34f3c7f
--- /dev/null
+++ b/examples/rust/src/print.rs
@@ -0,0 +1,18 @@
+#![no_std]
+#![no_main]
+
+#[cfg(not(test))]
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+ core::arch::wasm32::unreachable()
+}
+
+#[link(wasm_import_module = "env")]
+extern "C" {
+ fn printi32(x: i32);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn add_and_print(lh: i32, rh: i32) {
+ printi32(lh + rh);
+}
diff --git a/examples/rust/src/tinywasm.rs b/examples/rust/src/tinywasm.rs
index 0fb9261..08c8135 100644
--- a/examples/rust/src/tinywasm.rs
+++ b/examples/rust/src/tinywasm.rs
@@ -12,7 +12,7 @@ pub extern "C" fn hello() {
}
fn run() -> tinywasm::Result<()> {
- let module = tinywasm::Module::parse_bytes(include_bytes!("../out/hello.wasm"))?;
+ let module = tinywasm::Module::parse_bytes(include_bytes!("../out/print.wasm"))?;
let mut store = tinywasm::Store::default();
let mut imports = tinywasm::Imports::new();
diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs
index 3b26863..6580bd5 100644
--- a/examples/wasm-rust.rs
+++ b/examples/wasm-rust.rs
@@ -7,12 +7,15 @@ fn main() -> Result<()> {
println!("Usage: cargo run --example wasm-rust <rust_example>");
println!("Available examples:");
println!(" hello");
- println!(" tinywasm");
+ println!(" printi32");
+ println!(" fibonacci - calculate fibonacci(30)");
+ println!(" tinywasm - run printi32 inside of tinywasm inside of itself");
return Ok(());
}
match args[1].as_str() {
"hello" => hello()?,
+ "printi32" => printi32()?,
"fibonacci" => fibonacci()?,
"tinywasm" => tinywasm()?,
_ => {}
@@ -51,6 +54,36 @@ fn hello() -> Result<()> {
let mut imports = Imports::new();
imports.define(
"env",
+ "print_utf8",
+ Extern::typed_func(|mut ctx: FuncContext<'_>, args: (i32, i32)| {
+ let mem = ctx.memory("memory")?;
+ let ptr = args.1 as usize;
+ let len = args.0 as usize;
+ let string = mem.load_string(ptr, len)?;
+ println!("{}", string);
+ Ok(())
+ }),
+ )?;
+
+ let instance = module.instantiate(&mut store, Some(imports))?;
+ let arg_ptr = instance.exported_func::<(), i32>(&mut store, "arg_ptr")?.call(&mut store, ())?;
+ let arg = b"world";
+
+ instance.exported_memory(&mut store, "memory")?.store(arg_ptr as usize, arg.len(), arg)?;
+ let hello = instance.exported_func::<i32, ()>(&mut store, "hello")?;
+ hello.call(&mut store, arg.len() as i32)?;
+
+ Ok(())
+}
+
+fn printi32() -> Result<()> {
+ const HELLO_WASM: &[u8] = include_bytes!("./rust/out/print.wasm");
+ let module = Module::parse_bytes(&HELLO_WASM)?;
+ let mut store = Store::default();
+
+ let mut imports = Imports::new();
+ imports.define(
+ "env",
"printi32",
Extern::typed_func(|_: FuncContext<'_>, x: i32| {
println!("{}", x);