summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHenry Gressmann <mail@henrygressmann.de>2024-01-29 22:38:06 +0100
committerHenry Gressmann <mail@henrygressmann.de>2024-01-29 22:38:06 +0100
commit963ddd26a89490458e31d9d553dffafe5e350e96 (patch)
tree7a23b0d135e004667f9e0154e89a6ec31b225ff1
parentf8651619e576ac97209b72660144568b54eb965c (diff)
chore: add more examples, refactoring
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml1
-rw-r--r--README.md27
-rw-r--r--crates/cli/README.md10
-rw-r--r--crates/parser/README.md10
-rw-r--r--crates/tinywasm/Cargo.toml2
-rw-r--r--crates/tinywasm/src/func.rs1
-rw-r--r--crates/tinywasm/src/imports.rs31
-rw-r--r--crates/tinywasm/src/instance.rs6
-rw-r--r--crates/tinywasm/src/lib.rs2
-rw-r--r--crates/tinywasm/src/store/global.rs2
-rw-r--r--crates/tinywasm/src/store/mod.rs12
-rw-r--r--crates/tinywasm/src/store/table.rs8
-rw-r--r--crates/tinywasm/tests/test-wast.rs4
-rw-r--r--crates/types/README.md4
-rw-r--r--crates/types/src/archive.rs45
-rw-r--r--crates/types/src/lib.rs236
-rw-r--r--crates/types/src/value.rs175
-rw-r--r--examples/archive.rs29
-rw-r--r--examples/linking.rs41
-rw-r--r--examples/simple.rs22
-rw-r--r--examples/wasm-rust.rs16
22 files changed, 378 insertions, 307 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 6da632f..8f97f2b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2355,6 +2355,7 @@ dependencies = [
"wasmer",
"wasmi",
"wasmtime",
+ "wat",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index f52bc22..3bbbb35 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,6 +44,7 @@ color-eyre="0.6"
criterion={version="0.5", features=["html_reports"]}
tinywasm={path="crates/tinywasm"}
+wat={version="1.0"}
wasmi={version="0.31", features=["std"]}
wasmer={version="4.2", features=["cranelift", "singlepass"]}
wasmtime={version="17.0", features=["cranelift"]}
diff --git a/README.md b/README.md
index e7a66ae..8a4aa93 100644
--- a/README.md
+++ b/README.md
@@ -10,18 +10,17 @@
[![docs.rs](https://img.shields.io/docsrs/tinywasm?logo=rust)](https://docs.rs/tinywasm) [![Crates.io](https://img.shields.io/crates/v/tinywasm.svg?logo=rust)](https://crates.io/crates/tinywasm) [![Crates.io](https://img.shields.io/crates/l/tinywasm.svg)](./LICENSE-APACHE)
-# Why TinyWasm?
+## Why TinyWasm?
- **Tiny** - Designed to be as small as possible without sacrificing too much performance or functionality.
- **Fast enough** - TinyWasm is reasonably fast, especially when compared to other interpreters. See [Performance](#performance) for more details.
- **Portable** - Runs on any platform llvm supports, including WebAssembly. Minimal external dependencies.
-# Status
+## Status
-TinyWasm, starting from version `0.3.0`, passes all the WebAssembly 1.0 tests in the [WebAssembly Test Suite](https://github.com/WebAssembly/testsuite). The 2.0 tests are in progress. This is enough to run most WebAssembly programs, including TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)).
+TinyWasm, starting from version `0.3.0`, passes all the WebAssembly 1.0 tests in the [WebAssembly Test Suite](https://github.com/WebAssembly/testsuite). The 2.0 tests are in progress. This is enough to run most WebAssembly programs, including TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)). Results of the testsuite can be found [here](https://github.com/explodingcamera/tinywasm/tree/main/crates/tinywasm/tests/generated).
Some APIs to interact with the runtime are not yet exposed, and the existing ones are subject to change, but the core functionality is mostly complete.
-Results of the tests can be found [here](https://github.com/explodingcamera/tinywasm/tree/main/crates/tinywasm/tests/generated).
TinyWasm is not designed for performance, but rather for simplicity, size and portability. However, it is still reasonably fast, especially when compared to other interpreters. See [Performance](#performance) for more details.
@@ -32,24 +31,26 @@ TinyWasm is not designed for performance, but rather for simplicity, size and po
- [**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) - **Fully implemented** (as of version `0.4.0`)
- [**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)
+- [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) - **_Partially implemented_**
+- [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) - **_Partially implemented_**
## Usage
TinyWasm can be used through the `tinywasm-cli` CLI tool or as a library in your Rust project. Documentation can be found [here](https://docs.rs/tinywasm).
-### CLI
+### Library
```sh
-$ cargo install tinywasm-cli
-$ tinywasm-cli --help
+$ cargo add tinywasm
```
-### Library
+### CLI
+
+The CLI is mainly available for testing purposes, but can also be used to run WebAssembly programs.
```sh
-$ cargo add tinywasm
+$ cargo install tinywasm-cli
+$ tinywasm-cli --help
```
## Feature Flags
@@ -60,6 +61,8 @@ $ cargo add tinywasm
Enables logging using the `log` crate. This is enabled by default.
- **`parser`**\
Enables the `tinywasm-parser` crate. This is enabled by default.
+- **`archive`**\
+ Enables pre-parsing of archives. This is enabled by default.
- **`unsafe`**\
Uses `unsafe` code to improve performance, particularly in Memory access
@@ -70,7 +73,7 @@ Since `libm` is not as performant as the compiler's math intrinsics, it is recom
> Benchmarks are coming soon.
-# 📄 License
+## License
Licensed under either of [Apache License, Version 2.0](./LICENSE-APACHE) or [MIT license](./LICENSE-MIT) at your option.
diff --git a/crates/cli/README.md b/crates/cli/README.md
new file mode 100644
index 0000000..1f7a1bb
--- /dev/null
+++ b/crates/cli/README.md
@@ -0,0 +1,10 @@
+# `tinywasm-cli`
+
+The `tinywasm-cli` crate contains the command line interface for the `tinywasm` project. See [`tinywasm`](https://crates.io/crates/tinywasm) for more information.
+
+## Usage
+
+```bash
+$ cargo install tinywasm-cli
+$ tinywasm-cli --help
+```
diff --git a/crates/parser/README.md b/crates/parser/README.md
index 6cf2234..8ac7a30 100644
--- a/crates/parser/README.md
+++ b/crates/parser/README.md
@@ -1,6 +1,6 @@
# `tinywasm-parser`
-This crate provides a parser that can parse WebAssembly modules into a TinyWasm module. It is based on
+This crate provides a parser that can parse WebAssembly modules into a TinyWasm module. It is based on
[`wasmparser_nostd`](https://crates.io/crates/wasmparser_nostd) and used by [`tinywasm`](https://crates.io/crates/tinywasm).
## Features
@@ -11,11 +11,11 @@ This crate provides a parser that can parse WebAssembly modules into a TinyWasm
## Usage
```rust
-use tinywasm_parser::{Parser, TinyWasmModule};
+use tinywasm_parser::Parser;
let bytes = include_bytes!("./file.wasm");
let parser = Parser::new();
-let module: TinyWasmModule = parser.parse_module_bytes(bytes).unwrap();
-let mudule: TinyWasmModule = parser.parse_module_file("path/to/file.wasm").unwrap();
-let module: TinyWasmModule = parser.parse_module_stream(&mut stream).unwrap();
+let module = parser.parse_module_bytes(bytes).unwrap();
+let mudule = parser.parse_module_file("path/to/file.wasm").unwrap();
+let module = parser.parse_module_stream(&mut stream).unwrap();
```
diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml
index dd76f3d..bbc85e9 100644
--- a/crates/tinywasm/Cargo.toml
+++ b/crates/tinywasm/Cargo.toml
@@ -30,7 +30,7 @@ pretty_env_logger="0.5"
[features]
default=["std", "parser", "logging", "archive"]
-logging=["log", "tinywasm-types/logging", "tinywasm-parser?/logging"]
+logging=["log", "tinywasm-parser?/logging", "tinywasm-types/logging"]
std=["tinywasm-parser?/std", "tinywasm-types/std"]
parser=["tinywasm-parser"]
unsafe=["tinywasm-types/unsafe"]
diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs
index a0c1212..2088494 100644
--- a/crates/tinywasm/src/func.rs
+++ b/crates/tinywasm/src/func.rs
@@ -29,7 +29,6 @@ impl FuncHandle {
// 4. If the length of the provided argument values is different from the number of expected arguments, then fail
if unlikely(func_ty.params.len() != params.len()) {
- log::info!("func_ty.params: {:?}", func_ty.params);
return Err(Error::Other(format!(
"param count mismatch: expected {}, got {}",
func_ty.params.len(),
diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs
index 38d0707..e273838 100644
--- a/crates/tinywasm/src/imports.rs
+++ b/crates/tinywasm/src/imports.rs
@@ -286,7 +286,6 @@ impl Imports {
if let Some(v) = self.values.get(&name) {
return Some(ResolvedExtern::Extern(v.clone()));
}
-
if let Some(addr) = self.modules.get(&name.module) {
let instance = store.get_module_instance(*addr)?;
return Some(ResolvedExtern::Store(instance.export_addr(&import.name)?));
@@ -295,15 +294,11 @@ impl Imports {
None
}
- fn compare_types<T>(import: &Import, actual: &T, expected: &T) -> Result<()>
- where
- T: Debug + PartialEq,
- {
+ fn compare_types<T: Debug + PartialEq>(import: &Import, actual: &T, expected: &T) -> Result<()> {
if expected != actual {
log::error!("failed to link import {}, expected {:?}, got {:?}", import.name, expected, actual);
return Err(LinkingError::incompatible_import_type(import).into());
}
-
Ok(())
}
@@ -333,22 +328,20 @@ impl Imports {
) -> Result<()> {
Self::compare_types(import, &expected.arch, &actual.arch)?;
- if actual.page_count_initial > expected.page_count_initial {
- if let Some(real_size) = real_size {
- if actual.page_count_initial > real_size as u64 {
- return Err(LinkingError::incompatible_import_type(import).into());
- }
- } else {
- return Err(LinkingError::incompatible_import_type(import).into());
- }
+ if actual.page_count_initial > expected.page_count_initial
+ && real_size.map_or(true, |size| actual.page_count_initial > size as u64)
+ {
+ return Err(LinkingError::incompatible_import_type(import).into());
}
- match (expected.page_count_max, actual.page_count_max) {
- (None, Some(_)) => return Err(LinkingError::incompatible_import_type(import).into()),
- (Some(expected_max), Some(actual_max)) if actual_max < expected_max => {
- return Err(LinkingError::incompatible_import_type(import).into())
+ if expected.page_count_max.is_none() && actual.page_count_max.is_some() {
+ return Err(LinkingError::incompatible_import_type(import).into());
+ }
+
+ if let (Some(expected_max), Some(actual_max)) = (expected.page_count_max, actual.page_count_max) {
+ if actual_max < expected_max {
+ return Err(LinkingError::incompatible_import_type(import).into());
}
- _ => {}
}
Ok(())
diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs
index ad6c0f1..cb12f1b 100644
--- a/crates/tinywasm/src/instance.rs
+++ b/crates/tinywasm/src/instance.rs
@@ -124,11 +124,6 @@ impl ModuleInstance {
&self.0.func_addrs
}
- /// Get the module's function types
- pub fn func_tys(&self) -> &[FuncType] {
- &self.0.types
- }
-
pub(crate) fn new(inner: ModuleInstanceInner) -> Self {
Self(Rc::new(inner))
}
@@ -232,7 +227,6 @@ impl ModuleInstance {
///
/// 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 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/lib.rs b/crates/tinywasm/src/lib.rs
index d786ab8..79b111c 100644
--- a/crates/tinywasm/src/lib.rs
+++ b/crates/tinywasm/src/lib.rs
@@ -58,6 +58,8 @@
//! # Ok::<(), tinywasm::Error>(())
//! ```
//!
+//! For more examples, see the [`examples`](https://github.com/explodingcamera/tinywasm/tree/main/examples) directory.
+//!
//! ## Imports
//!
//! To provide imports to a module, you can use the [`Imports`] struct.
diff --git a/crates/tinywasm/src/store/global.rs b/crates/tinywasm/src/store/global.rs
index fbcc402..298a31e 100644
--- a/crates/tinywasm/src/store/global.rs
+++ b/crates/tinywasm/src/store/global.rs
@@ -30,9 +30,11 @@ impl GlobalInstance {
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/mod.rs b/crates/tinywasm/src/store/mod.rs
index be885a7..c8c5d8a 100644
--- a/crates/tinywasm/src/store/mod.rs
+++ b/crates/tinywasm/src/store/mod.rs
@@ -17,12 +17,7 @@ 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::*;
+pub(crate) use {data::*, element::*, function::*, global::*, memory::*, table::*};
// global store id counter
static STORE_ID: AtomicUsize = AtomicUsize::new(0);
@@ -205,12 +200,11 @@ impl Store {
let addr = globals.get(*addr as usize).copied().ok_or_else(|| {
Error::Other(format!("global {} not found. This should have been caught by the validator", addr))
})?;
-
let global = self.data.globals[addr as usize].clone();
let val = i64::from(global.borrow().value);
- log::error!("global: {}", val);
+
+ // check if the global is actually a null reference
if val < 0 {
- // the global is actually a null reference
None
} else {
Some(val as u32)
diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs
index 7b4c568..ea520b8 100644
--- a/crates/tinywasm/src/store/table.rs
+++ b/crates/tinywasm/src/store/table.rs
@@ -1,12 +1,8 @@
use crate::log;
+use crate::{Error, Result, Trap};
use alloc::{vec, vec::Vec};
-
use tinywasm_types::*;
-use crate::{
- Error, Result, Trap,
-};
-
const MAX_TABLE_SIZE: u32 = 10000000;
/// A WebAssembly Table Instance
@@ -30,7 +26,7 @@ impl TableInstance {
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),
+ _ => Err(Error::UnsupportedFeature("non-ref table".into()))?,
})
}
diff --git a/crates/tinywasm/tests/test-wast.rs b/crates/tinywasm/tests/test-wast.rs
index a50a612..1d3fbe3 100644
--- a/crates/tinywasm/tests/test-wast.rs
+++ b/crates/tinywasm/tests/test-wast.rs
@@ -13,8 +13,8 @@ fn main() -> Result<()> {
}
if args.len() < 3 {
- bail!("usage: cargo test-wast <wast-file>");
- }
+ bail!("usage: cargo test-wast <wast-file>")
+ };
// cwd for relative paths, absolute paths are kept as-is
let cwd = std::env::current_dir()?;
diff --git a/crates/types/README.md b/crates/types/README.md
index f2d048b..5a4431e 100644
--- a/crates/types/README.md
+++ b/crates/types/README.md
@@ -1,3 +1,3 @@
-# `tinywasm_types`
+# `tinywasm-types`
-This crate contains the types used by the [`tinywasm`](https://crates.io/crates/tinywasm) crate. It is also used by the [`tinywasm_parser`](https://crates.io/crates/tinywasm_parser) crate to parse WebAssembly binaries.
+This crate contains the types used by the [`tinywasm`](https://crates.io/crates/tinywasm) crate. It is also used by the [`tinywasm-parser`](https://crates.io/crates/tinywasm-parser) crate to parse WebAssembly binaries.
diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs
index 00a9911..bbd2206 100644
--- a/crates/types/src/archive.rs
+++ b/crates/types/src/archive.rs
@@ -1,3 +1,5 @@
+use core::fmt::{Display, Formatter};
+
use crate::TinyWasmModule;
use rkyv::{
check_archived_root,
@@ -14,30 +16,49 @@ const TWASM_MAGIC: [u8; 16] = [ TWASM_MAGIC_PREFIX[0], TWASM_MAGIC_PREFIX[1], TW
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");
+fn validate_magic(wasm: &[u8]) -> Result<usize, TwasmError> {
+ if wasm.len() < TWASM_MAGIC.len() || &wasm[..TWASM_MAGIC_PREFIX.len()] != TWASM_MAGIC_PREFIX {
+ return Err(TwasmError::InvalidMagic);
}
if &wasm[TWASM_MAGIC_PREFIX.len()..TWASM_MAGIC_PREFIX.len() + TWASM_VERSION.len()] != TWASM_VERSION {
- return Err("Invalid twasm: invalid version");
+ return Err(TwasmError::InvalidVersion);
}
if wasm[TWASM_MAGIC_PREFIX.len() + TWASM_VERSION.len()..TWASM_MAGIC.len()] != [0; 10] {
- return Err("Invalid twasm: invalid padding");
+ return Err(TwasmError::InvalidPadding);
}
Ok(TWASM_MAGIC.len())
}
+#[derive(Debug)]
+pub enum TwasmError {
+ InvalidMagic,
+ InvalidVersion,
+ InvalidPadding,
+ InvalidArchive,
+}
+
+impl Display for TwasmError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+ match self {
+ TwasmError::InvalidMagic => write!(f, "Invalid twasm: invalid magic number"),
+ TwasmError::InvalidVersion => write!(f, "Invalid twasm: invalid version"),
+ TwasmError::InvalidPadding => write!(f, "Invalid twasm: invalid padding"),
+ TwasmError::InvalidArchive => write!(f, "Invalid twasm: invalid archive"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for TwasmError {}
+
impl TinyWasmModule {
/// Creates a TinyWasmModule from a slice of bytes.
- pub fn from_twasm(wasm: &[u8]) -> Result<TinyWasmModule, &str> {
+ pub fn from_twasm(wasm: &[u8]) -> Result<TinyWasmModule, TwasmError> {
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"
+ let root = check_archived_root::<Self>(&wasm[len..]).map_err(|_e| {
+ crate::log::error!("Invalid archive: {}", _e);
+ TwasmError::InvalidArchive
})?;
Ok(root.deserialize(&mut rkyv::Infallible).unwrap())
diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs
index 547379c..205ec5a 100644
--- a/crates/types/src/lib.rs
+++ b/crates/types/src/lib.rs
@@ -10,6 +10,8 @@
//! Types used by [`tinywasm`](https://docs.rs/tinywasm) and [`tinywasm_parser`](https://docs.rs/tinywasm_parser).
extern crate alloc;
+use alloc::boxed::Box;
+use core::{fmt::Debug, ops::Range};
// log for logging (optional).
#[cfg(feature = "logging")]
@@ -24,10 +26,9 @@ pub(crate) mod log {
}
mod instructions;
-use core::{fmt::Debug, ops::Range};
-
-use alloc::boxed::Box;
+mod value;
pub use instructions::*;
+pub use value::*;
#[cfg(feature = "archive")]
pub mod archive;
@@ -73,235 +74,6 @@ pub struct TinyWasmModule {
pub elements: Box<[Element]>,
}
-/// A WebAssembly value.
-///
-/// See <https://webassembly.github.io/spec/core/syntax/types.html#value-types>
-#[derive(Clone, Copy)]
-pub enum WasmValue {
- // Num types
- /// A 32-bit integer.
- I32(i32),
- /// A 64-bit integer.
- I64(i64),
- /// A 32-bit float.
- F32(f32),
- /// A 64-bit float.
- F64(f64),
-
- RefExtern(ExternAddr),
- RefFunc(FuncAddr),
- RefNull(ValType),
-}
-
-impl WasmValue {
- #[inline]
- pub fn const_instr(&self) -> ConstInstruction {
- match self {
- Self::I32(i) => ConstInstruction::I32Const(*i),
- Self::I64(i) => ConstInstruction::I64Const(*i),
- Self::F32(i) => ConstInstruction::F32Const(*i),
- Self::F64(i) => ConstInstruction::F64Const(*i),
-
- Self::RefFunc(i) => ConstInstruction::RefFunc(*i),
- Self::RefNull(ty) => ConstInstruction::RefNull(*ty),
-
- // Self::RefExtern(addr) => ConstInstruction::RefExtern(*addr),
- _ => unimplemented!("no const_instr for {:?}", self),
- }
- }
-
- /// Get the default value for a given type.
- #[inline]
- pub fn default_for(ty: ValType) -> Self {
- match ty {
- ValType::I32 => Self::I32(0),
- ValType::I64 => Self::I64(0),
- ValType::F32 => Self::F32(0.0),
- ValType::F64 => Self::F64(0.0),
- ValType::RefFunc => Self::RefNull(ValType::RefFunc),
- ValType::RefExtern => Self::RefNull(ValType::RefExtern),
- }
- }
-
- #[inline]
- pub fn eq_loose(&self, other: &Self) -> bool {
- match (self, other) {
- (Self::I32(a), Self::I32(b)) => a == b,
- (Self::I64(a), Self::I64(b)) => a == b,
- (Self::RefNull(v), Self::RefNull(v2)) => v == v2,
- (Self::RefExtern(addr), Self::RefExtern(addr2)) => addr == addr2,
- (Self::RefFunc(addr), Self::RefFunc(addr2)) => addr == addr2,
- (Self::F32(a), Self::F32(b)) => {
- if a.is_nan() && b.is_nan() {
- true // Both are NaN, treat them as equal
- } else {
- a.to_bits() == b.to_bits()
- }
- }
- (Self::F64(a), Self::F64(b)) => {
- if a.is_nan() && b.is_nan() {
- true // Both are NaN, treat them as equal
- } else {
- a.to_bits() == b.to_bits()
- }
- }
- _ => false,
- }
- }
-}
-
-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(())
- }
- }
- }
-}
-
-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(())
- }
- }
- }
-}
-
-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(())
- }
- }
- }
-}
-
-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(())
- }
- }
- }
-}
-
-impl Debug for WasmValue {
- fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
- match self {
- WasmValue::I32(i) => write!(f, "i32({})", i),
- WasmValue::I64(i) => write!(f, "i64({})", i),
- WasmValue::F32(i) => write!(f, "f32({})", i),
- WasmValue::F64(i) => write!(f, "f64({})", i),
- WasmValue::RefExtern(addr) => write!(f, "ref.extern({:?})", addr),
- WasmValue::RefFunc(addr) => write!(f, "ref.func({:?})", addr),
- WasmValue::RefNull(ty) => write!(f, "ref.null({:?})", ty),
- // WasmValue::V128(i) => write!(f, "v128({})", i),
- }
- }
-}
-
-impl WasmValue {
- /// Get the type of a [`WasmValue`]
- #[inline]
- pub fn val_type(&self) -> ValType {
- match self {
- Self::I32(_) => ValType::I32,
- Self::I64(_) => ValType::I64,
- Self::F32(_) => ValType::F32,
- Self::F64(_) => ValType::F64,
- Self::RefExtern(_) => ValType::RefExtern,
- Self::RefFunc(_) => ValType::RefFunc,
- Self::RefNull(ty) => *ty,
- }
- }
-}
-
-/// 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,
- /// A 64-bit integer.
- I64,
- /// A 32-bit float.
- F32,
- /// A 64-bit float.
- F64,
- /// A reference to a function.
- RefFunc,
- /// A reference to an external value.
- RefExtern,
-}
-
-impl ValType {
- #[inline]
- pub fn default_value(&self) -> WasmValue {
- WasmValue::default_for(*self)
- }
-}
-
/// A WebAssembly External Kind.
///
/// See <https://webassembly.github.io/spec/core/syntax/types.html#external-types>
diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs
new file mode 100644
index 0000000..e46092b
--- /dev/null
+++ b/crates/types/src/value.rs
@@ -0,0 +1,175 @@
+use core::fmt::Debug;
+
+use crate::{ConstInstruction, ExternAddr, FuncAddr};
+
+/// A WebAssembly value.
+///
+/// See <https://webassembly.github.io/spec/core/syntax/types.html#value-types>
+#[derive(Clone, Copy)]
+pub enum WasmValue {
+ // Num types
+ /// A 32-bit integer.
+ I32(i32),
+ /// A 64-bit integer.
+ I64(i64),
+ /// A 32-bit float.
+ F32(f32),
+ /// A 64-bit float.
+ F64(f64),
+
+ RefExtern(ExternAddr),
+ RefFunc(FuncAddr),
+ RefNull(ValType),
+}
+
+impl WasmValue {
+ #[inline]
+ pub fn const_instr(&self) -> ConstInstruction {
+ match self {
+ Self::I32(i) => ConstInstruction::I32Const(*i),
+ Self::I64(i) => ConstInstruction::I64Const(*i),
+ Self::F32(i) => ConstInstruction::F32Const(*i),
+ Self::F64(i) => ConstInstruction::F64Const(*i),
+
+ Self::RefFunc(i) => ConstInstruction::RefFunc(*i),
+ Self::RefNull(ty) => ConstInstruction::RefNull(*ty),
+
+ // Self::RefExtern(addr) => ConstInstruction::RefExtern(*addr),
+ _ => unimplemented!("no const_instr for {:?}", self),
+ }
+ }
+
+ /// Get the default value for a given type.
+ #[inline]
+ pub fn default_for(ty: ValType) -> Self {
+ match ty {
+ ValType::I32 => Self::I32(0),
+ ValType::I64 => Self::I64(0),
+ ValType::F32 => Self::F32(0.0),
+ ValType::F64 => Self::F64(0.0),
+ ValType::RefFunc => Self::RefNull(ValType::RefFunc),
+ ValType::RefExtern => Self::RefNull(ValType::RefExtern),
+ }
+ }
+
+ #[inline]
+ pub fn eq_loose(&self, other: &Self) -> bool {
+ match (self, other) {
+ (Self::I32(a), Self::I32(b)) => a == b,
+ (Self::I64(a), Self::I64(b)) => a == b,
+ (Self::RefNull(v), Self::RefNull(v2)) => v == v2,
+ (Self::RefExtern(addr), Self::RefExtern(addr2)) => addr == addr2,
+ (Self::RefFunc(addr), Self::RefFunc(addr2)) => addr == addr2,
+ (Self::F32(a), Self::F32(b)) => {
+ if a.is_nan() && b.is_nan() {
+ true // Both are NaN, treat them as equal
+ } else {
+ a.to_bits() == b.to_bits()
+ }
+ }
+ (Self::F64(a), Self::F64(b)) => {
+ if a.is_nan() && b.is_nan() {
+ true // Both are NaN, treat them as equal
+ } else {
+ a.to_bits() == b.to_bits()
+ }
+ }
+ _ => false,
+ }
+ }
+}
+
+#[cold]
+fn cold() {}
+
+impl Debug for WasmValue {
+ fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
+ match self {
+ WasmValue::I32(i) => write!(f, "i32({})", i),
+ WasmValue::I64(i) => write!(f, "i64({})", i),
+ WasmValue::F32(i) => write!(f, "f32({})", i),
+ WasmValue::F64(i) => write!(f, "f64({})", i),
+ WasmValue::RefExtern(addr) => write!(f, "ref.extern({:?})", addr),
+ WasmValue::RefFunc(addr) => write!(f, "ref.func({:?})", addr),
+ WasmValue::RefNull(ty) => write!(f, "ref.null({:?})", ty),
+ }
+ }
+}
+
+impl WasmValue {
+ /// Get the type of a [`WasmValue`]
+ #[inline]
+ pub fn val_type(&self) -> ValType {
+ match self {
+ Self::I32(_) => ValType::I32,
+ Self::I64(_) => ValType::I64,
+ Self::F32(_) => ValType::F32,
+ Self::F64(_) => ValType::F64,
+ Self::RefExtern(_) => ValType::RefExtern,
+ Self::RefFunc(_) => ValType::RefFunc,
+ Self::RefNull(ty) => *ty,
+ }
+ }
+}
+
+/// 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,
+ /// A 64-bit integer.
+ I64,
+ /// A 32-bit float.
+ F32,
+ /// A 64-bit float.
+ F64,
+ /// A reference to a function.
+ RefFunc,
+ /// A reference to an external value.
+ RefExtern,
+}
+
+impl ValType {
+ #[inline]
+ pub fn default_value(&self) -> WasmValue {
+ WasmValue::default_for(*self)
+ }
+}
+
+macro_rules! impl_conversion_for_wasmvalue {
+ ($($t:ty => $variant:ident),*) => {
+ $(
+ // Implementing From<$t> for WasmValue
+ impl From<$t> for WasmValue {
+ #[inline]
+ fn from(i: $t) -> Self {
+ Self::$variant(i)
+ }
+ }
+
+ // Implementing TryFrom<WasmValue> for $t
+ impl TryFrom<WasmValue> for $t {
+ type Error = ();
+
+ #[inline]
+ fn try_from(value: WasmValue) -> Result<Self, Self::Error> {
+ if let WasmValue::$variant(i) = value {
+ Ok(i)
+ } else {
+ cold();
+ Err(())
+ }
+ }
+ }
+ )*
+ }
+}
+
+impl_conversion_for_wasmvalue! {
+ i32 => I32,
+ i64 => I64,
+ f32 => F32,
+ f64 => F64
+}
diff --git a/examples/archive.rs b/examples/archive.rs
new file mode 100644
index 0000000..7c93205
--- /dev/null
+++ b/examples/archive.rs
@@ -0,0 +1,29 @@
+use color_eyre::eyre::Result;
+use tinywasm::{parser::Parser, types::TinyWasmModule, Module, Store};
+
+const WASM: &str = r#"
+(module
+ (func $add (param $lhs i32) (param $rhs i32) (result i32)
+ local.get $lhs
+ local.get $rhs
+ i32.add)
+ (export "add" (func $add)))
+"#;
+
+fn main() -> Result<()> {
+ let wasm = wat::parse_str(WASM).expect("failed to parse wat");
+ let module = Parser::default().parse_module_bytes(&wasm)?;
+ let twasm = module.serialize_twasm();
+
+ // now, you could e.g. write twasm to a file called `add.twasm`
+ // and load it later in a different program
+
+ let module: Module = TinyWasmModule::from_twasm(&twasm)?.into();
+ let mut store = Store::default();
+ let instance = module.instantiate(&mut store, None)?;
+ let add = instance.exported_func::<(i32, i32), i32>(&store, "add")?;
+
+ assert_eq!(add.call(&mut store, (1, 2))?, 3);
+
+ Ok(())
+}
diff --git a/examples/linking.rs b/examples/linking.rs
new file mode 100644
index 0000000..f278266
--- /dev/null
+++ b/examples/linking.rs
@@ -0,0 +1,41 @@
+use color_eyre::eyre::Result;
+use tinywasm::{Module, Store};
+
+const WASM_ADD: &str = r#"
+(module
+ (func $add (param $lhs i32) (param $rhs i32) (result i32)
+ local.get $lhs
+ local.get $rhs
+ i32.add)
+ (export "add" (func $add)))
+"#;
+
+const WASM_IMPORT: &str = r#"
+(module
+ (import "adder" "add" (func $add (param i32 i32) (result i32)))
+ (func $main (result i32)
+ i32.const 1
+ i32.const 2
+ call $add)
+ (export "main" (func $main))
+)
+"#;
+
+fn main() -> Result<()> {
+ let wasm_add = wat::parse_str(WASM_ADD).expect("failed to parse wat");
+ let wasm_import = wat::parse_str(WASM_IMPORT).expect("failed to parse wat");
+
+ let add_module = Module::parse_bytes(&wasm_add)?;
+ let import_module = Module::parse_bytes(&wasm_import)?;
+
+ let mut store = Store::default();
+ let add_instance = add_module.instantiate(&mut store, None)?;
+
+ let mut imports = tinywasm::Imports::new();
+ imports.link_module("adder", add_instance.id())?;
+ let import_instance = import_module.instantiate(&mut store, Some(imports))?;
+
+ let main = import_instance.exported_func::<(), i32>(&store, "main")?;
+ assert_eq!(main.call(&mut store, ())?, 3);
+ Ok(())
+}
diff --git a/examples/simple.rs b/examples/simple.rs
new file mode 100644
index 0000000..6f79c0f
--- /dev/null
+++ b/examples/simple.rs
@@ -0,0 +1,22 @@
+use color_eyre::eyre::Result;
+use tinywasm::{Module, Store};
+
+const WASM: &str = r#"
+(module
+ (func $add (param $lhs i32) (param $rhs i32) (result i32)
+ local.get $lhs
+ local.get $rhs
+ i32.add)
+ (export "add" (func $add)))
+"#;
+
+fn main() -> Result<()> {
+ let wasm = wat::parse_str(WASM).expect("failed to parse wat");
+ let module = Module::parse_bytes(&wasm)?;
+ let mut store = Store::default();
+ let instance = module.instantiate(&mut store, None)?;
+ let add = instance.exported_func::<(i32, i32), i32>(&store, "add")?;
+
+ assert_eq!(add.call(&mut store, (1, 2))?, 3);
+ Ok(())
+}
diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs
index 112222c..b57a1da 100644
--- a/examples/wasm-rust.rs
+++ b/examples/wasm-rust.rs
@@ -1,6 +1,22 @@
use color_eyre::eyre::Result;
use tinywasm::{Extern, FuncContext, Imports, MemoryStringExt, Module, Store};
+/// Examples of using WebAssembly compiled from Rust with tinywasm.
+///
+/// These examples are meant to be run with `cargo run --example wasm-rust <example>`.
+/// For example, `cargo run --example wasm-rust hello`.
+///
+/// To run these, you first need to compile the Rust examples to WebAssembly:
+///
+/// ```sh
+/// ./examples/rust/build.sh
+/// ```
+///
+/// This requires the `wasm32-unknown-unknown` target, `binaryen` and `wabt` to be installed.
+/// `rustup target add wasm32-unknown-unknown`.
+/// https://github.com/WebAssembly/wabt
+/// https://github.com/WebAssembly/binaryen
+///
fn main() -> Result<()> {
let args = std::env::args().collect::<Vec<_>>();
if args.len() < 2 {