diff options
| -rw-r--r-- | ARCHITECTURE.md | 3 | ||||
| -rw-r--r-- | Cargo.lock | 31 | ||||
| -rw-r--r-- | benches/fibonacci.rs | 6 | ||||
| -rw-r--r-- | benches/selfhosted.rs | 4 | ||||
| -rw-r--r-- | crates/parser/Cargo.toml | 4 | ||||
| -rw-r--r-- | crates/parser/src/lib.rs | 22 | ||||
| -rw-r--r-- | crates/tinywasm/Cargo.toml | 5 | ||||
| -rw-r--r-- | crates/tinywasm/src/store/data.rs | 27 | ||||
| -rw-r--r-- | crates/tinywasm/src/store/element.rs | 19 | ||||
| -rw-r--r-- | crates/tinywasm/src/store/function.rs | 11 | ||||
| -rw-r--r-- | crates/tinywasm/src/store/global.rs | 39 | ||||
| -rw-r--r-- | crates/tinywasm/src/store/memory.rs | 162 | ||||
| -rw-r--r-- | crates/tinywasm/src/store/mod.rs | 218 | ||||
| -rw-r--r-- | crates/tinywasm/src/store/table.rs | 123 | ||||
| -rw-r--r-- | crates/tinywasm/tests/testsuite/run.rs | 6 | ||||
| -rw-r--r-- | crates/types/Cargo.toml | 10 | ||||
| -rw-r--r-- | crates/types/src/archive.rs | 92 | ||||
| -rw-r--r-- | crates/types/src/instructions.rs | 14 | ||||
| -rw-r--r-- | crates/types/src/lib.rs | 114 | ||||
| -rw-r--r-- | examples/rust/src/tinywasm_no_std.rs | 33 | ||||
| -rw-r--r-- | examples/wasm-rust.rs | 18 |
21 files changed, 588 insertions, 373 deletions
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 28607da..d3816d0 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -17,7 +17,8 @@ Some key differences are: ## Bytecode Format To improve performance and reduce code size, instructions are encoded as enum variants instead of opcodes. -This allows preprocessing the bytecode into a more compact format, which can be loaded directly into memory and executed without decoding later. This can skip the decoding step entirely on resource-constrained devices where memory is limited. +This allows preprocessing the bytecode into a more compact format, which can be loaded directly into memory and executed without decoding later. This can skip the decoding step entirely on resource-constrained devices where memory is limited. See this [blog post](https://wasmer.io/posts/improving-with-zero-copy-deserialization) by Wasmer +for more details which inspired this design. Some instructions are split into multiple variants to reduce the size of the enum (e.g. `br_table` and `br_label`). Additionally, label instructions contain offsets relative to the current instruction to make branching faster and easier to implement. @@ -227,7 +227,18 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" dependencies = [ - "bytecheck_derive", + "bytecheck_derive 0.6.11", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41502630fe304ce54cbb2f8389e017784dee2b0328147779fcbe43b9db06d35d" +dependencies = [ + "bytecheck_derive 0.7.0", "ptr_meta", "simdutf8", ] @@ -244,6 +255,17 @@ dependencies = [ ] [[package]] +name = "bytecheck_derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda88c587085bc07dc201ab9df871bd9baa5e07f7754b745e4d7194b43ac1eda" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] name = "bytemuck" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1931,7 +1953,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" dependencies = [ - "bytecheck", + "bytecheck 0.6.11", ] [[package]] @@ -1941,7 +1963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527a97cdfef66f65998b5f3b637c26f5a5ec09cc52a3f9932313ac645f4190f5" dependencies = [ "bitvec", - "bytecheck", + "bytecheck 0.6.11", "bytes", "hashbrown 0.12.3", "indexmap 1.9.3", @@ -2339,6 +2361,7 @@ dependencies = [ name = "tinywasm-types" version = "0.3.0" dependencies = [ + "bytecheck 0.7.0", "log", "rkyv", ] @@ -2710,7 +2733,7 @@ version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae2c892882f0b416783fb4310e5697f5c30587f6f9555f9d4f2be85ab39d5d3d" dependencies = [ - "bytecheck", + "bytecheck 0.6.11", "enum-iterator", "enumset", "indexmap 1.9.3", diff --git a/benches/fibonacci.rs b/benches/fibonacci.rs index 5d8a0bd..7c77ebf 100644 --- a/benches/fibonacci.rs +++ b/benches/fibonacci.rs @@ -9,7 +9,7 @@ fn run_tinywasm(module: TinyWasmModule, iterations: i32) { let mut store = Store::default(); let imports = Imports::default(); let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); - let hello = instance.exported_func::<i32, i32>(&mut store, "fibonacci").expect("exported_func"); + let hello = instance.exported_func::<i32, i32>(&store, "fibonacci").expect("exported_func"); hello.call(&mut store, iterations).expect("call"); } @@ -29,8 +29,8 @@ fn criterion_benchmark(c: &mut Criterion) { let module = tinywasm_module(FIBONACCI); let mut group = c.benchmark_group("fibonacci"); - group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone(), black_box(50)))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi(black_box(50)))); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone(), black_box(60)))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi(black_box(60)))); } criterion_group!( diff --git a/benches/selfhosted.rs b/benches/selfhosted.rs index f78d958..b9df1af 100644 --- a/benches/selfhosted.rs +++ b/benches/selfhosted.rs @@ -10,7 +10,7 @@ fn run_tinywasm(module: TinyWasmModule) { let mut imports = Imports::default(); imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(()))).expect("define"); let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); - let hello = instance.exported_func::<(), ()>(&mut store, "hello").expect("exported_func"); + let hello = instance.exported_func::<(), ()>(&store, "hello").expect("exported_func"); hello.call(&mut store, ()).expect("call"); } @@ -32,7 +32,7 @@ fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("selfhosted"); group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone()))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi())); + group.bench_function("wasmi", |b| b.iter(run_wasmi)); } criterion_group!( diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 1592f85..df8acce 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -11,9 +11,9 @@ repository.workspace=true # fork of wasmparser with no_std support, see https://github.com/bytecodealliance/wasmtime/issues/3495 wasmparser={version="0.100", package="wasmparser-nostd", default-features=false} log={version="0.4", optional=true} -tinywasm-types={version="0.3.0-alpha.0", path="../types"} +tinywasm-types={version="0.3.0-alpha.0", path="../types", default-features=false} [features] default=["std", "logging"] logging=["log"] -std=[] +std=["tinywasm-types/std"] diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index edcd280..c608232 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -25,7 +25,7 @@ mod module; use alloc::{string::ToString, vec::Vec}; pub use error::*; use module::ModuleReader; -use tinywasm_types::WasmFunction; +use tinywasm_types::{TypedWasmFunction, WasmFunction}; use wasmparser::Validator; pub use tinywasm_types::TinyWasmModule; @@ -116,19 +116,13 @@ impl TryFrom<ModuleReader> for TinyWasmModule { .code .into_iter() .zip(code_type_addrs) - .map(|(f, ty_idx)| { - ( - ty_idx, - WasmFunction { - instructions: f.body, - locals: f.locals, - ty: reader - .func_types - .get(ty_idx as usize) - .expect("No func type for func, this is a bug") - .clone(), - }, - ) + .map(|(f, ty_idx)| TypedWasmFunction { + type_addr: ty_idx, + wasm_function: WasmFunction { + instructions: f.body, + locals: f.locals, + ty: reader.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone(), + }, }) .collect::<Vec<_>>(); diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index a9e24bf..dd76f3d 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -29,11 +29,12 @@ plotters={version="0.3"} pretty_env_logger="0.5" [features] -default=["std", "parser", "logging"] +default=["std", "parser", "logging", "archive"] logging=["log", "tinywasm-types/logging", "tinywasm-parser?/logging"] std=["tinywasm-parser?/std", "tinywasm-types/std"] parser=["tinywasm-parser"] -unsafe=[] +unsafe=["tinywasm-types/unsafe"] +archive=["tinywasm-types/archive"] [[test]] name="generate-charts" diff --git a/crates/tinywasm/src/store/data.rs b/crates/tinywasm/src/store/data.rs new file mode 100644 index 0000000..efbb858 --- /dev/null +++ b/crates/tinywasm/src/store/data.rs @@ -0,0 +1,27 @@ +use alloc::vec::Vec; +use tinywasm_types::*; + +/// A WebAssembly Data Instance +/// +/// See <https://webassembly.github.io/spec/core/exec/runtime.html#data-instances> +#[derive(Debug)] +pub(crate) struct DataInstance { + pub(crate) data: Option<Vec<u8>>, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl DataInstance { + pub(crate) fn new(data: Option<Vec<u8>>, owner: ModuleInstanceAddr) -> Self { + Self { data, _owner: owner } + } + + pub(crate) fn drop(&mut self) -> Option<()> { + match self.data { + None => None, + Some(_) => { + let _ = self.data.take(); + Some(()) + } + } + } +} diff --git a/crates/tinywasm/src/store/element.rs b/crates/tinywasm/src/store/element.rs new file mode 100644 index 0000000..6563dff --- /dev/null +++ b/crates/tinywasm/src/store/element.rs @@ -0,0 +1,19 @@ +use crate::TableElement; +use alloc::vec::Vec; +use tinywasm_types::*; + +/// A WebAssembly Element Instance +/// +/// See <https://webassembly.github.io/spec/core/exec/runtime.html#element-instances> +#[derive(Debug)] +pub(crate) struct ElementInstance { + pub(crate) kind: ElementKind, + pub(crate) items: Option<Vec<TableElement>>, // none is the element was dropped + _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl ElementInstance { + pub(crate) fn new(kind: ElementKind, owner: ModuleInstanceAddr, items: Option<Vec<TableElement>>) -> Self { + Self { kind, _owner: owner, items } + } +} diff --git a/crates/tinywasm/src/store/function.rs b/crates/tinywasm/src/store/function.rs new file mode 100644 index 0000000..7508d00 --- /dev/null +++ b/crates/tinywasm/src/store/function.rs @@ -0,0 +1,11 @@ +use crate::Function; +use tinywasm_types::*; + +#[derive(Debug, Clone)] +/// A WebAssembly Function Instance +/// +/// See <https://webassembly.github.io/spec/core/exec/runtime.html#function-instances> +pub(crate) struct FunctionInstance { + pub(crate) func: Function, + pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions +} diff --git a/crates/tinywasm/src/store/global.rs b/crates/tinywasm/src/store/global.rs new file mode 100644 index 0000000..fbcc402 --- /dev/null +++ b/crates/tinywasm/src/store/global.rs @@ -0,0 +1,39 @@ +use alloc::{format, string::ToString}; +use tinywasm_types::*; + +use crate::{runtime::RawWasmValue, Error, Result}; + +/// A WebAssembly Global Instance +/// +/// See <https://webassembly.github.io/spec/core/exec/runtime.html#global-instances> +#[derive(Debug)] +pub(crate) struct GlobalInstance { + pub(crate) value: RawWasmValue, + pub(crate) ty: GlobalType, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +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(()) + } +} diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index a087b1d..9b527d3 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -205,122 +205,92 @@ pub(crate) unsafe trait MemLoadable<const T: usize>: Sized + Copy { fn from_be_bytes(bytes: [u8; T]) -> Self; } -#[allow(unsafe_code)] -unsafe impl MemLoadable<1> for u8 { - fn from_le_bytes(bytes: [u8; 1]) -> Self { - bytes[0] - } - fn from_be_bytes(bytes: [u8; 1]) -> Self { - bytes[0] - } -} +macro_rules! impl_mem_loadable_for_primitive { + ($($type:ty, $size:expr),*) => { + $( + #[allow(unsafe_code)] + unsafe impl MemLoadable<$size> for $type { + fn from_le_bytes(bytes: [u8; $size]) -> Self { + <$type>::from_le_bytes(bytes) + } -#[allow(unsafe_code)] -unsafe impl MemLoadable<2> for u16 { - fn from_le_bytes(bytes: [u8; 2]) -> Self { - Self::from_le_bytes(bytes) - } - fn from_be_bytes(bytes: [u8; 2]) -> Self { - Self::from_be_bytes(bytes) + fn from_be_bytes(bytes: [u8; $size]) -> Self { + <$type>::from_be_bytes(bytes) + } + } + )* } } -#[allow(unsafe_code)] -unsafe impl MemLoadable<4> for u32 { - fn from_le_bytes(bytes: [u8; 4]) -> Self { - Self::from_le_bytes(bytes) - } - fn from_be_bytes(bytes: [u8; 4]) -> Self { - Self::from_be_bytes(bytes) - } -} +impl_mem_loadable_for_primitive!( + u8, 1, i8, 1, u16, 2, i16, 2, u32, 4, i32, 4, f32, 4, u64, 8, i64, 8, f64, 8, u128, 16, i128, 16 +); -#[allow(unsafe_code)] -unsafe impl MemLoadable<8> for u64 { - fn from_le_bytes(bytes: [u8; 8]) -> Self { - Self::from_le_bytes(bytes) - } - fn from_be_bytes(bytes: [u8; 8]) -> Self { - Self::from_be_bytes(bytes) - } -} +#[cfg(test)] +mod memory_instance_tests { + use super::*; + use tinywasm_types::{MemoryArch, MemoryType, ModuleInstanceAddr}; -#[allow(unsafe_code)] -unsafe impl MemLoadable<16> for u128 { - fn from_le_bytes(bytes: [u8; 16]) -> Self { - Self::from_le_bytes(bytes) + fn create_test_memory() -> MemoryInstance { + let kind = MemoryType { arch: MemoryArch::I32, page_count_initial: 1, page_count_max: Some(2) }; + let owner = ModuleInstanceAddr::default(); + MemoryInstance::new(kind, owner) } - fn from_be_bytes(bytes: [u8; 16]) -> Self { - Self::from_be_bytes(bytes) - } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<1> for i8 { - fn from_le_bytes(bytes: [u8; 1]) -> Self { - bytes[0] as i8 - } - fn from_be_bytes(bytes: [u8; 1]) -> Self { - bytes[0] as i8 + #[test] + fn test_memory_store_and_load() { + let mut memory = create_test_memory(); + let data_to_store = [1, 2, 3, 4]; + assert!(memory.store(0, 0, &data_to_store, data_to_store.len()).is_ok()); + let loaded_data = memory.load(0, 0, data_to_store.len()).unwrap(); + assert_eq!(loaded_data, &data_to_store); } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<2> for i16 { - fn from_le_bytes(bytes: [u8; 2]) -> Self { - Self::from_le_bytes(bytes) - } - fn from_be_bytes(bytes: [u8; 2]) -> Self { - Self::from_be_bytes(bytes) + #[test] + fn test_memory_store_out_of_bounds() { + let mut memory = create_test_memory(); + let data_to_store = [1, 2, 3, 4]; + assert!(memory.store(memory.data.len(), 0, &data_to_store, data_to_store.len()).is_err()); } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<4> for i32 { - fn from_le_bytes(bytes: [u8; 4]) -> Self { - Self::from_le_bytes(bytes) + #[test] + fn test_memory_fill() { + let mut memory = create_test_memory(); + assert!(memory.fill(0, 10, 42).is_ok()); + assert_eq!(&memory.data[0..10], &[42; 10]); } - fn from_be_bytes(bytes: [u8; 4]) -> Self { - Self::from_be_bytes(bytes) - } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<8> for i64 { - fn from_le_bytes(bytes: [u8; 8]) -> Self { - Self::from_le_bytes(bytes) - } - fn from_be_bytes(bytes: [u8; 8]) -> Self { - Self::from_be_bytes(bytes) + #[test] + fn test_memory_fill_out_of_bounds() { + let mut memory = create_test_memory(); + assert!(memory.fill(memory.data.len(), 10, 42).is_err()); } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<16> for i128 { - fn from_le_bytes(bytes: [u8; 16]) -> Self { - Self::from_le_bytes(bytes) - } - fn from_be_bytes(bytes: [u8; 16]) -> Self { - Self::from_be_bytes(bytes) + #[test] + fn test_memory_copy_within() { + let mut memory = create_test_memory(); + memory.fill(0, 10, 1).unwrap(); + assert!(memory.copy_within(10, 0, 10).is_ok()); + assert_eq!(&memory.data[10..20], &[1; 10]); } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<4> for f32 { - fn from_le_bytes(bytes: [u8; 4]) -> Self { - Self::from_le_bytes(bytes) + #[test] + fn test_memory_copy_within_out_of_bounds() { + let mut memory = create_test_memory(); + assert!(memory.copy_within(memory.data.len(), 0, 10).is_err()); } - fn from_be_bytes(bytes: [u8; 4]) -> Self { - Self::from_be_bytes(bytes) - } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<8> for f64 { - fn from_le_bytes(bytes: [u8; 8]) -> Self { - Self::from_le_bytes(bytes) + #[test] + fn test_memory_grow() { + let mut memory = create_test_memory(); + let original_pages = memory.page_count(); + assert_eq!(memory.grow(1), Some(original_pages as i32)); + assert_eq!(memory.page_count(), original_pages + 1); } - fn from_be_bytes(bytes: [u8; 8]) -> Self { - Self::from_be_bytes(bytes) + + #[test] + fn test_memory_grow_out_of_bounds() { + let mut memory = create_test_memory(); + assert!(memory.grow(MAX_PAGES as i32 + 1).is_none()); } } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 1526eb1..be885a7 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -1,5 +1,5 @@ use crate::log; -use alloc::{boxed::Box, format, rc::Rc, string::ToString, vec, vec::Vec}; +use alloc::{boxed::Box, format, rc::Rc, string::ToString, vec::Vec}; use core::{ cell::RefCell, sync::atomic::{AtomicUsize, Ordering}, @@ -11,8 +11,18 @@ use crate::{ Error, Function, ModuleInstance, Result, Trap, }; +mod data; +mod element; +mod function; +mod global; mod memory; +mod table; +pub(crate) use data::*; +pub(crate) use element::*; +pub(crate) use function::*; +pub(crate) use global::*; pub(crate) use memory::*; +pub(crate) use table::*; // global store id counter static STORE_ID: AtomicUsize = AtomicUsize::new(0); @@ -118,14 +128,14 @@ impl Store { /// Add functions to the store, returning their addresses in the store pub(crate) fn init_funcs( &mut self, - funcs: Vec<(u32, WasmFunction)>, + funcs: Vec<TypedWasmFunction>, idx: ModuleInstanceAddr, ) -> Result<Vec<FuncAddr>> { let func_count = self.data.funcs.len(); let mut func_addrs = Vec::with_capacity(func_count); - for (i, (_, func)) in funcs.into_iter().enumerate() { - self.data.funcs.push(FunctionInstance { func: Function::Wasm(Rc::new(func)), owner: idx }); + for (i, func) in funcs.into_iter().enumerate() { + self.data.funcs.push(FunctionInstance { func: Function::Wasm(Rc::new(func.wasm_function)), owner: idx }); func_addrs.push((i + func_count) as FuncAddr); } @@ -448,203 +458,3 @@ impl Store { .map(|global| global.borrow_mut().value = value) } } - -#[derive(Debug, Clone)] -/// A WebAssembly Function Instance -/// -/// See <https://webassembly.github.io/spec/core/exec/runtime.html#function-instances> -pub(crate) struct FunctionInstance { - pub(crate) func: Function, - pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum TableElement { - Uninitialized, - Initialized(Addr), -} - -impl From<Option<Addr>> for TableElement { - fn from(addr: Option<Addr>) -> Self { - match addr { - None => TableElement::Uninitialized, - Some(addr) => TableElement::Initialized(addr), - } - } -} - -impl TableElement { - pub(crate) fn addr(&self) -> Option<Addr> { - match self { - TableElement::Uninitialized => None, - TableElement::Initialized(addr) => Some(*addr), - } - } - - pub(crate) fn map<F: FnOnce(Addr) -> Addr>(self, f: F) -> Self { - match self { - TableElement::Uninitialized => TableElement::Uninitialized, - TableElement::Initialized(addr) => TableElement::Initialized(f(addr)), - } - } -} - -const MAX_TABLE_SIZE: u32 = 10000000; - -/// A WebAssembly Table Instance -/// -/// See <https://webassembly.github.io/spec/core/exec/runtime.html#table-instances> -#[derive(Debug)] -pub(crate) struct TableInstance { - pub(crate) elements: Vec<TableElement>, - pub(crate) kind: TableType, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances -} - -impl TableInstance { - pub(crate) fn new(kind: TableType, owner: ModuleInstanceAddr) -> Self { - Self { elements: vec![TableElement::Uninitialized; kind.size_initial as usize], kind, _owner: owner } - } - - pub(crate) fn get_wasm_val(&self, addr: usize) -> Result<WasmValue> { - let val = self.get(addr)?.addr(); - - Ok(match self.kind.element_type { - ValType::RefFunc => val.map(WasmValue::RefFunc).unwrap_or(WasmValue::RefNull(ValType::RefFunc)), - ValType::RefExtern => val.map(WasmValue::RefExtern).unwrap_or(WasmValue::RefNull(ValType::RefExtern)), - _ => unimplemented!("unsupported table type: {:?}", self.kind.element_type), - }) - } - - pub(crate) fn get(&self, addr: usize) -> Result<&TableElement> { - self.elements.get(addr).ok_or_else(|| Error::Trap(Trap::UndefinedElement { index: addr })) - } - - pub(crate) fn set(&mut self, table_idx: usize, value: Addr) -> Result<()> { - self.grow_to_fit(table_idx + 1).map(|_| self.elements[table_idx] = TableElement::Initialized(value)) - } - - pub(crate) fn grow_to_fit(&mut self, new_size: usize) -> Result<()> { - if new_size > self.elements.len() { - if new_size > self.kind.size_max.unwrap_or(MAX_TABLE_SIZE) as usize { - return Err(crate::Trap::TableOutOfBounds { offset: new_size, len: 1, max: self.elements.len() }.into()); - } - - self.elements.resize(new_size, TableElement::Uninitialized); - } - Ok(()) - } - - pub(crate) fn size(&self) -> i32 { - self.elements.len() as i32 - } - - fn resolve_func_ref(&self, func_addrs: &[u32], addr: Addr) -> Addr { - if self.kind.element_type != ValType::RefFunc { - return addr; - } - - *func_addrs - .get(addr as usize) - .expect("error initializing table: function not found. This should have been caught by the validator") - } - - // Initialize the table with the given elements - pub(crate) fn init_raw(&mut self, offset: i32, init: &[TableElement]) -> Result<()> { - let offset = offset as usize; - let end = offset.checked_add(init.len()).ok_or_else(|| { - Error::Trap(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }) - })?; - - if end > self.elements.len() || end < offset { - return Err(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }.into()); - } - - self.elements[offset..end].copy_from_slice(init); - log::debug!("table: {:?}", self.elements); - Ok(()) - } - - // Initialize the table with the given elements (resolves function references) - pub(crate) fn init(&mut self, func_addrs: &[u32], offset: i32, init: &[TableElement]) -> Result<()> { - let init = init.iter().map(|item| item.map(|addr| self.resolve_func_ref(func_addrs, addr))).collect::<Vec<_>>(); - - self.init_raw(offset, &init) - } -} - -/// A WebAssembly Global Instance -/// -/// See <https://webassembly.github.io/spec/core/exec/runtime.html#global-instances> -#[derive(Debug)] -pub(crate) struct GlobalInstance { - pub(crate) value: RawWasmValue, - pub(crate) ty: GlobalType, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances -} - -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 -/// -/// See <https://webassembly.github.io/spec/core/exec/runtime.html#element-instances> -#[derive(Debug)] -pub(crate) struct ElementInstance { - pub(crate) kind: ElementKind, - pub(crate) items: Option<Vec<TableElement>>, // none is the element was dropped - _owner: ModuleInstanceAddr, // index into store.module_instances -} - -impl ElementInstance { - pub(crate) fn new(kind: ElementKind, owner: ModuleInstanceAddr, items: Option<Vec<TableElement>>) -> Self { - Self { kind, _owner: owner, items } - } -} - -/// A WebAssembly Data Instance -/// -/// See <https://webassembly.github.io/spec/core/exec/runtime.html#data-instances> -#[derive(Debug)] -pub(crate) struct DataInstance { - pub(crate) data: Option<Vec<u8>>, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances -} - -impl DataInstance { - pub(crate) fn new(data: Option<Vec<u8>>, owner: ModuleInstanceAddr) -> Self { - Self { data, _owner: owner } - } - - pub(crate) fn drop(&mut self) -> Option<()> { - match self.data { - None => None, - Some(_) => { - let _ = self.data.take(); - Some(()) - } - } - } -} diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs new file mode 100644 index 0000000..7b4c568 --- /dev/null +++ b/crates/tinywasm/src/store/table.rs @@ -0,0 +1,123 @@ +use crate::log; +use alloc::{vec, vec::Vec}; + +use tinywasm_types::*; + +use crate::{ + Error, Result, Trap, +}; + +const MAX_TABLE_SIZE: u32 = 10000000; + +/// A WebAssembly Table Instance +/// +/// See <https://webassembly.github.io/spec/core/exec/runtime.html#table-instances> +#[derive(Debug)] +pub(crate) struct TableInstance { + pub(crate) elements: Vec<TableElement>, + pub(crate) kind: TableType, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl TableInstance { + pub(crate) fn new(kind: TableType, owner: ModuleInstanceAddr) -> Self { + Self { elements: vec![TableElement::Uninitialized; kind.size_initial as usize], kind, _owner: owner } + } + + pub(crate) fn get_wasm_val(&self, addr: usize) -> Result<WasmValue> { + let val = self.get(addr)?.addr(); + + Ok(match self.kind.element_type { + ValType::RefFunc => val.map(WasmValue::RefFunc).unwrap_or(WasmValue::RefNull(ValType::RefFunc)), + ValType::RefExtern => val.map(WasmValue::RefExtern).unwrap_or(WasmValue::RefNull(ValType::RefExtern)), + _ => unimplemented!("unsupported table type: {:?}", self.kind.element_type), + }) + } + + pub(crate) fn get(&self, addr: usize) -> Result<&TableElement> { + self.elements.get(addr).ok_or_else(|| Error::Trap(Trap::UndefinedElement { index: addr })) + } + + pub(crate) fn set(&mut self, table_idx: usize, value: Addr) -> Result<()> { + self.grow_to_fit(table_idx + 1).map(|_| self.elements[table_idx] = TableElement::Initialized(value)) + } + + pub(crate) fn grow_to_fit(&mut self, new_size: usize) -> Result<()> { + if new_size > self.elements.len() { + if new_size > self.kind.size_max.unwrap_or(MAX_TABLE_SIZE) as usize { + return Err(crate::Trap::TableOutOfBounds { offset: new_size, len: 1, max: self.elements.len() }.into()); + } + + self.elements.resize(new_size, TableElement::Uninitialized); + } + Ok(()) + } + + pub(crate) fn size(&self) -> i32 { + self.elements.len() as i32 + } + + fn resolve_func_ref(&self, func_addrs: &[u32], addr: Addr) -> Addr { + if self.kind.element_type != ValType::RefFunc { + return addr; + } + + *func_addrs + .get(addr as usize) + .expect("error initializing table: function not found. This should have been caught by the validator") + } + + // Initialize the table with the given elements + pub(crate) fn init_raw(&mut self, offset: i32, init: &[TableElement]) -> Result<()> { + let offset = offset as usize; + let end = offset.checked_add(init.len()).ok_or_else(|| { + Error::Trap(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }) + })?; + + if end > self.elements.len() || end < offset { + return Err(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }.into()); + } + + self.elements[offset..end].copy_from_slice(init); + log::debug!("table: {:?}", self.elements); + Ok(()) + } + + // Initialize the table with the given elements (resolves function references) + pub(crate) fn init(&mut self, func_addrs: &[u32], offset: i32, init: &[TableElement]) -> Result<()> { + let init = init.iter().map(|item| item.map(|addr| self.resolve_func_ref(func_addrs, addr))).collect::<Vec<_>>(); + + self.init_raw(offset, &init) + } +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum TableElement { + Uninitialized, + Initialized(TableAddr), +} + +impl From<Option<Addr>> for TableElement { + fn from(addr: Option<Addr>) -> Self { + match addr { + None => TableElement::Uninitialized, + Some(addr) => TableElement::Initialized(addr), + } + } +} + +impl TableElement { + pub(crate) fn addr(&self) -> Option<Addr> { + match self { + TableElement::Uninitialized => None, + TableElement::Initialized(addr) => Some(*addr), + } + } + + pub(crate) fn map<F: FnOnce(Addr) -> Addr>(self, f: F) -> Self { + match self { + TableElement::Uninitialized => TableElement::Uninitialized, + TableElement::Initialized(addr) => TableElement::Initialized(f(addr)), + } + } +} diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index de5d5ab..125a9d6 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -137,7 +137,7 @@ impl TestSuite { for (name, addr) in modules { log::debug!("registering module: {}", name); - imports.link_module(&name, *addr)?; + imports.link_module(name, *addr)?; } Ok(imports) @@ -199,7 +199,7 @@ impl TestSuite { QuoteWat::QuoteModule(_, quoted_wat) => { let wat = quoted_wat .iter() - .map(|(_, s)| std::str::from_utf8(&s).expect("failed to convert wast to utf8")) + .map(|(_, s)| std::str::from_utf8(s).expect("failed to convert wast to utf8")) .collect::<Vec<_>>() .join("\n"); @@ -444,7 +444,7 @@ impl TestSuite { continue; } }; - let expected = expected.get(0).expect("expected global value"); + let expected = expected.first().expect("expected global value"); let module_global = module_global.attach_type(expected.val_type()); if !module_global.eq_loose(expected) { diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index bc6769a..76e5679 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -9,10 +9,12 @@ repository.workspace=true [dependencies] log={version="0.4", optional=true} -rkyv={version="0.7", optional=true, default-features=false, features=["size_32"]} +rkyv={version="0.7", optional=true, default-features=false, features=["size_32", "validation"]} +bytecheck={version="0.7", optional=true} [features] -default=["std", "logging"] -std=["rkyv/std"] -serialize=["dep:rkyv", "dep:log"] +default=["std", "logging", "archive", "unsafe"] +std=["rkyv?/std"] +archive=["dep:rkyv", "dep:bytecheck"] logging=["dep:log"] +unsafe=[] diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs new file mode 100644 index 0000000..00a9911 --- /dev/null +++ b/crates/types/src/archive.rs @@ -0,0 +1,92 @@ +use crate::TinyWasmModule; +use rkyv::{ + check_archived_root, + ser::{serializers::AllocSerializer, Serializer}, + Deserialize, +}; + +// 16 bytes +const TWASM_MAGIC_PREFIX: &[u8; 4] = b"TWAS"; +const TWASM_VERSION: &[u8; 2] = b"01"; + +#[rustfmt::skip] +const TWASM_MAGIC: [u8; 16] = [ TWASM_MAGIC_PREFIX[0], TWASM_MAGIC_PREFIX[1], TWASM_MAGIC_PREFIX[2], TWASM_MAGIC_PREFIX[3], TWASM_VERSION[0], TWASM_VERSION[1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + +pub use rkyv::AlignedVec; + +fn validate_magic(wasm: &[u8]) -> Result<usize, &str> { + if wasm.len() < TWASM_MAGIC.len() { + return Err("Invalid twasm: too short"); + } + if &wasm[..TWASM_MAGIC_PREFIX.len()] != TWASM_MAGIC_PREFIX { + return Err("Invalid twasm: invalid magic number"); + } + if &wasm[TWASM_MAGIC_PREFIX.len()..TWASM_MAGIC_PREFIX.len() + TWASM_VERSION.len()] != TWASM_VERSION { + return Err("Invalid twasm: invalid version"); + } + if wasm[TWASM_MAGIC_PREFIX.len() + TWASM_VERSION.len()..TWASM_MAGIC.len()] != [0; 10] { + return Err("Invalid twasm: invalid padding"); + } + + Ok(TWASM_MAGIC.len()) +} + +impl TinyWasmModule { + /// Creates a TinyWasmModule from a slice of bytes. + pub fn from_twasm(wasm: &[u8]) -> Result<TinyWasmModule, &str> { + let len = validate_magic(wasm)?; + let root = check_archived_root::<Self>(&wasm[len..]).map_err(|e| { + log::error!("Error checking archived root: {}", e); + "Error checking archived root" + })?; + + Ok(root.deserialize(&mut rkyv::Infallible).unwrap()) + } + + #[cfg(feature = "unsafe")] + #[allow(unsafe_code)] + /// Creates a TinyWasmModule from a slice of bytes. + /// + /// # Safety + /// This function is only safe to call if the bytes have been created by + /// a trusted source. Otherwise, it may cause undefined behavior. + pub unsafe fn from_twasm_unchecked(wasm: &[u8]) -> Self { + let len = validate_magic(wasm).unwrap(); + rkyv::archived_root::<TinyWasmModule>(&wasm[len..]).deserialize(&mut rkyv::Infallible).unwrap() + } + + /// Serializes the TinyWasmModule into a vector of bytes. + /// AlignedVec can be deferenced as a slice of bytes and + /// implements io::Write when the `std` feature is enabled. + pub fn serialize_twasm(&self) -> rkyv::AlignedVec { + let mut serializer = AllocSerializer::<0>::default(); + serializer.pad(TWASM_MAGIC.len()).unwrap(); + serializer.serialize_value(self).unwrap(); + let mut out = serializer.into_serializer().into_inner(); + out[..TWASM_MAGIC.len()].copy_from_slice(&TWASM_MAGIC); + out + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serialize() { + let wasm = TinyWasmModule::default(); + let twasm = wasm.serialize_twasm(); + let wasm2 = TinyWasmModule::from_twasm(&twasm).unwrap(); + assert_eq!(wasm, wasm2); + } + + #[cfg(feature = "unsafe")] + #[test] + fn test_serialize_unchecked() { + let wasm = TinyWasmModule::default(); + let twasm = wasm.serialize_twasm(); + #[allow(unsafe_code)] + let wasm2 = unsafe { TinyWasmModule::from_twasm_unchecked(&twasm) }; + assert_eq!(wasm, wasm2); + } +} diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 1061d10..0e2eafe 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -3,6 +3,8 @@ use crate::{DataAddr, ElemAddr, MemAddr}; use super::{FuncAddr, GlobalAddr, LabelAddr, LocalAddr, TableAddr, TypeAddr, ValType}; #[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum BlockArgs { Empty, Type(ValType), @@ -10,7 +12,9 @@ pub enum BlockArgs { } /// Represents a memory immediate in a WebAssembly memory instruction. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct MemoryArg { pub mem_addr: MemAddr, pub align: u8, @@ -23,7 +27,9 @@ type BrTableLen = usize; type EndOffset = usize; type ElseOffset = usize; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum ConstInstruction { I32Const(i32), I64Const(i64), @@ -46,7 +52,9 @@ pub enum ConstInstruction { /// This makes it easier to implement the label stack iteratively. /// /// See <https://webassembly.github.io/spec/core/binary/instructions.html> -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum Instruction { // Custom Instructions BrLabel(LabelAddr), diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 2ccf869..547379c 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -1,10 +1,11 @@ -#![no_std] -#![forbid(unsafe_code)] #![doc(test( no_crate_inject, attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] #![warn(missing_debug_implementations, rust_2018_idioms, unreachable_pub)] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "unsafe"), forbid(unsafe_code))] +#![cfg_attr(feature = "unsafe", deny(unused_unsafe))] //! Types used by [`tinywasm`](https://docs.rs/tinywasm) and [`tinywasm_parser`](https://docs.rs/tinywasm_parser). @@ -28,22 +29,25 @@ use core::{fmt::Debug, ops::Range}; use alloc::boxed::Box; pub use instructions::*; +#[cfg(feature = "archive")] +pub mod archive; + /// A TinyWasm WebAssembly Module /// /// This is the internal representation of a WebAssembly module in TinyWasm. /// TinyWasmModules are validated before being created, so they are guaranteed to be valid (as long as they were created by TinyWasm). /// This means you should not trust a TinyWasmModule created by a third party to be valid. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct TinyWasmModule { /// The version of the WebAssembly module. pub version: Option<u16>, - /// The start function of the WebAssembly module. pub start_func: Option<FuncAddr>, /// The functions of the WebAssembly module. - pub funcs: Box<[(u32, WasmFunction)]>, - + pub funcs: Box<[TypedWasmFunction]>, /// The types of the WebAssembly module. pub func_types: Box<[FuncType]>, @@ -90,6 +94,7 @@ pub enum WasmValue { } impl WasmValue { + #[inline] pub fn const_instr(&self) -> ConstInstruction { match self { Self::I32(i) => ConstInstruction::I32Const(*i), @@ -106,6 +111,7 @@ impl WasmValue { } /// Get the default value for a given type. + #[inline] pub fn default_for(ty: ValType) -> Self { match ty { ValType::I32 => Self::I32(0), @@ -117,6 +123,7 @@ impl WasmValue { } } + #[inline] pub fn eq_loose(&self, other: &Self) -> bool { match (self, other) { (Self::I32(a), Self::I32(b)) => a == b, @@ -144,36 +151,45 @@ impl WasmValue { } impl From<i32> for WasmValue { + #[inline] fn from(i: i32) -> Self { Self::I32(i) } } impl From<i64> for WasmValue { + #[inline] fn from(i: i64) -> Self { Self::I64(i) } } impl From<f32> for WasmValue { + #[inline] fn from(i: f32) -> Self { Self::F32(i) } } impl From<f64> for WasmValue { + #[inline] fn from(i: f64) -> Self { Self::F64(i) } } +#[cold] +fn cold() {} + impl TryFrom<WasmValue> for i32 { type Error = (); + #[inline] fn try_from(value: WasmValue) -> Result<Self, Self::Error> { match value { WasmValue::I32(i) => Ok(i), _ => { + cold(); crate::log::error!("i32: try_from failed: {:?}", value); Err(()) } @@ -184,10 +200,12 @@ impl TryFrom<WasmValue> for i32 { impl TryFrom<WasmValue> for i64 { type Error = (); + #[inline] fn try_from(value: WasmValue) -> Result<Self, Self::Error> { match value { WasmValue::I64(i) => Ok(i), _ => { + cold(); crate::log::error!("i64: try_from failed: {:?}", value); Err(()) } @@ -198,10 +216,12 @@ impl TryFrom<WasmValue> for i64 { impl TryFrom<WasmValue> for f32 { type Error = (); + #[inline] fn try_from(value: WasmValue) -> Result<Self, Self::Error> { match value { WasmValue::F32(i) => Ok(i), _ => { + cold(); crate::log::error!("f32: try_from failed: {:?}", value); Err(()) } @@ -212,10 +232,12 @@ impl TryFrom<WasmValue> for f32 { impl TryFrom<WasmValue> for f64 { type Error = (); + #[inline] fn try_from(value: WasmValue) -> Result<Self, Self::Error> { match value { WasmValue::F64(i) => Ok(i), _ => { + cold(); crate::log::error!("f64: try_from failed: {:?}", value); Err(()) } @@ -240,6 +262,7 @@ impl Debug for WasmValue { impl WasmValue { /// Get the type of a [`WasmValue`] + #[inline] pub fn val_type(&self) -> ValType { match self { Self::I32(_) => ValType::I32, @@ -255,6 +278,8 @@ impl WasmValue { /// Type of a WebAssembly value. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum ValType { /// A 32-bit integer. I32, @@ -271,6 +296,7 @@ pub enum ValType { } impl ValType { + #[inline] pub fn default_value(&self) -> WasmValue { WasmValue::default_for(*self) } @@ -280,6 +306,8 @@ impl ValType { /// /// See <https://webassembly.github.io/spec/core/syntax/types.html#external-types> #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum ExternalKind { /// A WebAssembly Function. Func, @@ -322,6 +350,7 @@ pub enum ExternVal { } impl ExternVal { + #[inline] pub fn kind(&self) -> ExternalKind { match self { Self::Func(_) => ExternalKind::Func, @@ -331,6 +360,7 @@ impl ExternVal { } } + #[inline] pub fn new(kind: ExternalKind, addr: Addr) -> Self { match kind { ExternalKind::Func => Self::Func(addr), @@ -345,6 +375,8 @@ impl ExternVal { /// /// See <https://webassembly.github.io/spec/core/syntax/types.html#function-types> #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct FuncType { pub params: Box<[ValType]>, pub results: Box<[ValType]>, @@ -352,20 +384,33 @@ pub struct FuncType { impl FuncType { /// Get the number of parameters of a function type. + #[inline] pub fn empty() -> Self { Self { params: Box::new([]), results: Box::new([]) } } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct WasmFunction { pub instructions: Box<[Instruction]>, pub locals: Box<[ValType]>, pub ty: FuncType, } +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] +pub struct TypedWasmFunction { + pub type_addr: u32, + pub wasm_function: WasmFunction, +} + /// A WebAssembly Module Export -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct Export { /// The name of the export. pub name: Box<str>, @@ -375,19 +420,25 @@ pub struct Export { pub index: u32, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct Global { pub ty: GlobalType, pub init: ConstInstruction, } #[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct GlobalType { pub mutable: bool, pub ty: ValType, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct TableType { pub element_type: ValType, pub size_initial: u32, @@ -404,10 +455,12 @@ impl TableType { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] /// Represents a memory's type. #[derive(Copy)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct MemoryType { pub arch: MemoryArch, pub page_count_initial: u64, @@ -418,30 +471,28 @@ impl MemoryType { pub fn new_32(page_count_initial: u64, page_count_max: Option<u64>) -> Self { Self { arch: MemoryArch::I32, page_count_initial, page_count_max } } - - // pub fn new_64(page_count_initial: u64, page_count_max: Option<u64>) -> Self { - // Self { - // arch: MemoryArch::I64, - // page_count_initial, - // page_count_max, - // } - // } } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum MemoryArch { I32, I64, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct Import { pub module: Box<str>, pub name: Box<str>, pub kind: ImportKind, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum ImportKind { Function(TypeAddr), Table(TableType), @@ -450,6 +501,7 @@ pub enum ImportKind { } impl From<&ImportKind> for ExternalKind { + #[inline] fn from(kind: &ImportKind) -> Self { match kind { ImportKind::Function(_) => Self::Func, @@ -460,20 +512,26 @@ impl From<&ImportKind> for ExternalKind { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct Data { pub data: Box<[u8]>, pub range: Range<usize>, pub kind: DataKind, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum DataKind { Active { mem: MemAddr, offset: ConstInstruction }, Passive, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct Element { pub kind: ElementKind, pub items: Box<[ElementItem]>, @@ -481,14 +539,18 @@ pub struct Element { pub ty: ValType, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum ElementKind { Passive, Active { table: TableAddr, offset: ConstInstruction }, Declared, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum ElementItem { Func(FuncAddr), Expr(ConstInstruction), diff --git a/examples/rust/src/tinywasm_no_std.rs b/examples/rust/src/tinywasm_no_std.rs new file mode 100644 index 0000000..9f2b28c --- /dev/null +++ b/examples/rust/src/tinywasm_no_std.rs @@ -0,0 +1,33 @@ +#![no_main] +#![no_std] +use tinywasm::{Extern, FuncContext}; + +#[link(wasm_import_module = "env")] +extern "C" { + fn printi32(x: i32); +} + +#[no_mangle] +pub extern "C" fn hello() { + let _ = run(); +} + +fn run() -> tinywasm::Result<()> { + let module = tinywasm::Module::parse_bytes(include_bytes!("../out/print.wasm"))?; + let mut store = tinywasm::Store::default(); + let mut imports = tinywasm::Imports::new(); + + imports.define( + "env", + "printi32", + Extern::typed_func(|_: FuncContext<'_>, v: i32| { + unsafe { printi32(v) } + Ok(()) + }), + )?; + let instance = module.instantiate(&mut store, Some(imports))?; + + let add_and_print = instance.exported_func::<(i32, i32), ()>(&mut store, "add_and_print")?; + add_and_print.call(&mut store, (1, 2))?; + Ok(()) +} diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 96e3419..112222c 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -26,7 +26,7 @@ fn main() -> Result<()> { fn tinywasm() -> Result<()> { const TINYWASM: &[u8] = include_bytes!("./rust/out/tinywasm.wasm"); - let module = Module::parse_bytes(&TINYWASM)?; + let module = Module::parse_bytes(TINYWASM)?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -40,7 +40,7 @@ fn tinywasm() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let hello = instance.exported_func::<(), ()>(&mut store, "hello")?; + let hello = instance.exported_func::<(), ()>(&store, "hello")?; hello.call(&mut store, ())?; Ok(()) @@ -48,7 +48,7 @@ fn tinywasm() -> Result<()> { fn hello() -> Result<()> { const HELLO_WASM: &[u8] = include_bytes!("./rust/out/hello.wasm"); - let module = Module::parse_bytes(&HELLO_WASM)?; + let module = Module::parse_bytes(HELLO_WASM)?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -66,11 +66,11 @@ fn hello() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let arg_ptr = instance.exported_func::<(), i32>(&mut store, "arg_ptr")?.call(&mut store, ())?; + let arg_ptr = instance.exported_func::<(), i32>(&store, "arg_ptr")?.call(&mut store, ())?; let arg = b"world"; instance.exported_memory_mut(&mut store, "memory")?.store(arg_ptr as usize, arg.len(), arg)?; - let hello = instance.exported_func::<i32, ()>(&mut store, "hello")?; + let hello = instance.exported_func::<i32, ()>(&store, "hello")?; hello.call(&mut store, arg.len() as i32)?; Ok(()) @@ -78,7 +78,7 @@ fn hello() -> Result<()> { fn printi32() -> Result<()> { const HELLO_WASM: &[u8] = include_bytes!("./rust/out/print.wasm"); - let module = Module::parse_bytes(&HELLO_WASM)?; + let module = Module::parse_bytes(HELLO_WASM)?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -92,7 +92,7 @@ fn printi32() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let add_and_print = instance.exported_func::<(i32, i32), ()>(&mut store, "add_and_print")?; + let add_and_print = instance.exported_func::<(i32, i32), ()>(&store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) @@ -100,11 +100,11 @@ fn printi32() -> Result<()> { fn fibonacci() -> Result<()> { const FIBONACCI_WASM: &[u8] = include_bytes!("./rust/out/fibonacci.wasm"); - let module = Module::parse_bytes(&FIBONACCI_WASM)?; + let module = Module::parse_bytes(FIBONACCI_WASM)?; let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let fibonacci = instance.exported_func::<i32, i32>(&mut store, "fibonacci")?; + let fibonacci = instance.exported_func::<i32, i32>(&store, "fibonacci")?; let n = 30; let result = fibonacci.call(&mut store, n)?; println!("fibonacci({}) = {}", n, result); |
