Skip to content

Commit

Permalink
docs: string example
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
  • Loading branch information
explodingcamera committed Jan 26, 2024
1 parent 6fd283b commit 8adce67
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions crates/tinywasm/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>> {
Expand Down
128 changes: 123 additions & 5 deletions crates/tinywasm/src/reference.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
2 changes: 1 addition & 1 deletion crates/tinywasm/src/runtime/interpreter/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())?;
}};
}

Expand Down
61 changes: 55 additions & 6 deletions crates/tinywasm/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}

Expand Down Expand Up @@ -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() })
})?;

Expand Down Expand Up @@ -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;
Expand All @@ -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"))
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions examples/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ tinywasm={path="../../crates/tinywasm", features=["parser", "std"]}
name="hello"
path="src/hello.rs"

[[bin]]
name="print"
path="src/print.rs"

[[bin]]
name="tinywasm"
path="src/tinywasm.rs"
Expand Down
4 changes: 2 additions & 2 deletions examples/rust/build.sh
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand Down
30 changes: 20 additions & 10 deletions examples/rust/src/hello.rs
Original file line number Diff line number Diff line change
@@ -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);
}
18 changes: 18 additions & 0 deletions examples/rust/src/print.rs
Original file line number Diff line number Diff line change
@@ -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);
}
2 changes: 1 addition & 1 deletion examples/rust/src/tinywasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Loading

0 comments on commit 8adce67

Please sign in to comment.