diff options
| author | Henry Gressmann <mail@henrygressmann.de> | 2024-01-26 12:03:59 +0100 |
|---|---|---|
| committer | Henry Gressmann <mail@henrygressmann.de> | 2024-01-26 12:03:59 +0100 |
| commit | f3d890a36d6299dbd70377ce57b0bd9d61de5c1b (patch) | |
| tree | 36b51022af641c0f464fd0c5d4456b722e5f735b | |
| parent | 461126ce309f99e4ad2a6ddcbc175ea01c5e8fee (diff) | |
docs: working selfhosted example
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Cargo.lock | 7 | ||||
| -rw-r--r-- | Cargo.toml | 9 | ||||
| -rw-r--r-- | README.md | 13 | ||||
| -rw-r--r-- | crates/tinywasm/src/error.rs | 40 | ||||
| -rw-r--r-- | examples/README.md | 22 | ||||
| -rw-r--r-- | examples/rust/Cargo.toml | 7 | ||||
| -rwxr-xr-x | examples/rust/build.sh | 18 | ||||
| -rw-r--r-- | examples/rust/src/tinywasm.rs | 47 | ||||
| -rw-r--r-- | examples/wasm-rust.rs | 54 | ||||
| -rw-r--r-- | examples/wasm/add.wasm | bin | 65 -> 0 bytes | |||
| -rw-r--r-- | examples/wasm/add.wat | 16 | ||||
| -rw-r--r-- | examples/wasm/call.wat | 42 | ||||
| -rw-r--r-- | examples/wasm/global.wat | 1 | ||||
| -rw-r--r-- | examples/wasm/helloworld.wasm | bin | 115 -> 0 bytes | |||
| -rw-r--r-- | examples/wasm/helloworld.wat | 15 | ||||
| -rw-r--r-- | examples/wasm/loop.wat | 84 | ||||
| -rw-r--r-- | examples/wasm/test.wat | 7 |
18 files changed, 155 insertions, 229 deletions
@@ -1,4 +1,4 @@ /target notes.md -examples/tinywasm.wat +examples/rust/out/* examples/wast/* @@ -1046,6 +1046,13 @@ dependencies = [ ] [[package]] +name = "rust-wasm-examples" +version = "0.0.0" +dependencies = [ + "tinywasm", +] + +[[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1,7 +1,14 @@ [workspace] -members=["crates/*"] +members=["crates/*", "examples/rust"] resolver="2" +[profile.wasm] +opt-level="z" +lto="thin" +codegen-units=1 +panic="abort" +inherits="release" + [workspace.package] version="0.3.0-alpha.0" edition="2021" @@ -12,7 +12,7 @@ # Status -TinyWasm, starting from upcoming 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 (notably `simd` and `bulk-memory-operations` are not implemented yet). +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 (notably `simd` and `bulk-memory-operations` are not implemented yet). This is enough to run most WebAssembly programs, including TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)). 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). @@ -56,16 +56,7 @@ $ cargo add tinywasm Enables the `tinywasm-parser` crate. This is enabled by default. With all these features disabled, TinyWasm only depends on `core`, `alloc` and `libm` and can be used in `no_std` environments. - -<!-- # 🎯 Goals - -- Self-hosted (can run itself compiled to WebAssembly) -- No unsafe code -- Works on `no_std` (with `alloc` the feature and nightly compiler) -- Fully support WebAssembly MVP (1.0) -- Low Memory Usage (less than 10kb) -- Fast Startup Time -- Preemptive multitasking support --> +Since `libm` is not as performant as the compiler's built-in math intrinsics, it is recommended to use the `std` feature if possible (at least [for now](https://github.com/rust-lang/rfcs/issues/2505)). ## Performance diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 0344e82..4f68eaa 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -186,8 +186,8 @@ impl Display for Error { #[cfg(feature = "std")] Self::Io(err) => write!(f, "I/O error: {}", err), - Self::Trap(trap) => write!(f, "trap: {}", trap.message()), - Self::Linker(err) => write!(f, "linking error: {}", err.message()), + Self::Trap(trap) => write!(f, "trap: {}", trap), + Self::Linker(err) => write!(f, "linking error: {}", err), Self::CallStackEmpty => write!(f, "call stack empty"), Self::InvalidLabelType => write!(f, "invalid label type"), Self::Other(message) => write!(f, "unknown error: {}", message), @@ -200,6 +200,42 @@ impl Display for Error { } } +impl Display for LinkingError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::UnknownImport { module, name } => write!(f, "unknown import: {}.{}", module, name), + Self::IncompatibleImportType { module, name } => { + write!(f, "incompatible import type: {}.{}", module, name) + } + } + } +} + +impl Display for Trap { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Unreachable => write!(f, "unreachable"), + Self::MemoryOutOfBounds { offset, len, max } => { + write!(f, "out of bounds memory access: offset={}, len={}, max={}", offset, len, max) + } + Self::TableOutOfBounds { offset, len, max } => { + write!(f, "out of bounds table access: offset={}, len={}, max={}", offset, len, max) + } + Self::DivisionByZero => write!(f, "integer divide by zero"), + Self::InvalidConversionToInt => write!(f, "invalid conversion to integer"), + Self::IntegerOverflow => write!(f, "integer overflow"), + Self::CallStackOverflow => write!(f, "call stack exhausted"), + Self::UndefinedElement { index } => write!(f, "undefined element: index={}", index), + Self::UninitializedElement { index } => { + write!(f, "uninitialized element: index={}", index) + } + Self::IndirectCallTypeMismatch { expected, actual } => { + write!(f, "indirect call type mismatch: expected={:?}, actual={:?}", expected, actual) + } + } + } +} + #[cfg(any(feature = "std", all(not(feature = "std"), nightly)))] impl crate::std::error::Error for Error {} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..ce47073 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,22 @@ +# Examples + +## WasmRust + +These are examples using WebAssembly generated from Rust code. +To run these, you first need to build the Rust code into WebAssembly, since the wasm files are not included in the repository to keep it small. +This requires the `wasm32-unknown-unknown` target and `wasm-opt` to be installed (available via Binaryen). + +```bash +$ ./examples/rust/build.sh +``` + +Then you can run the examples: + +```bash +$ cargo run --example wasm-rust <example> +``` + +Where `<example>` is one of the following: + +- `hello`: A simple example that prints a number to the console. +- `tinywasm`: Runs `hello` using TinyWasm - inside of TinyWasm itself! diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index 91b6aed..9ff80dc 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -7,8 +7,7 @@ forced-target="wasm32-unknown-unknown" edition="2021" [dependencies] -tinywasm={path="../../crates/tinywasm", default-features=false, features=["parser"]} -embedded-alloc={version="0.5"} +tinywasm={path="../../crates/tinywasm", features=["parser", "std"]} [[bin]] name="hello" @@ -17,7 +16,3 @@ path="src/hello.rs" [[bin]] name="tinywasm" path="src/tinywasm.rs" - -[profile.release] -opt-level="z" -panic="abort" diff --git a/examples/rust/build.sh b/examples/rust/build.sh new file mode 100755 index 0000000..2c8069a --- /dev/null +++ b/examples/rust/build.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +cd "$(dirname "$0")" + +bins=("hello" "tinywasm") +exclude_wat=("tinywasm") +out_dir="../../target/wasm32-unknown-unknown/wasm" +dest_dir="out" + +for bin in "${bins[@]}"; do + 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 + + if [[ ! " ${exclude_wat[@]} " =~ " $bin " ]]; then + wasm2wat "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wat" + fi +done diff --git a/examples/rust/src/tinywasm.rs b/examples/rust/src/tinywasm.rs index 4edde66..52af7b9 100644 --- a/examples/rust/src/tinywasm.rs +++ b/examples/rust/src/tinywasm.rs @@ -1,41 +1,32 @@ -#![no_std] #![no_main] +use tinywasm::{Extern, FuncContext}; -use embedded_alloc::Heap; -// use tinywasm::{Extern, FuncContext}; - -#[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); } -#[global_allocator] -static HEAP: Heap = Heap::empty(); - #[no_mangle] -pub unsafe extern "C" fn _start() { - // Initialize the allocator BEFORE you use it - { - use core::mem::MaybeUninit; - const HEAP_SIZE: usize = 1024; - static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } - } - - // now the allocator is ready types like Box, Vec can be used. +pub extern "C" fn hello() { let _ = run(); } fn run() -> tinywasm::Result<()> { - // let module = tinywasm::Module::parse_bytes(include_bytes!("../out/hello.wasm"))?; - // let mut store = tinywasm::Store::default(); + let module = tinywasm::Module::parse_bytes(include_bytes!("../../wasm/hello.wasm"))?; + let mut store = tinywasm::Store::default(); + let mut imports = tinywasm::Imports::new(); - // let mut imports = tinywasm::Imports::new(); - // imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(())))?; + imports.define( + "env", + "printi32", + Extern::typed_func(|_: FuncContext<'_>, v: i32| { + unsafe { printi32(v) } + Ok(()) + }), + )?; + let instance = module.instantiate(&mut store, Some(imports))?; - // let instance = module.instantiate(&mut store, Some(imports))?; - // let add_and_print = instance.typed_func::<(i32, i32), ()>(&mut store, "add_and_print")?; - // add_and_print.call(&mut store, (1, 2))?; + let add_and_print = instance.typed_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 21d3eb8..3b8877e 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -7,35 +7,59 @@ fn main() -> Result<()> { println!("Usage: cargo run --example wasm-rust <rust_example>"); println!("Available examples:"); println!(" hello"); + println!(" tinywasm"); return Ok(()); } match args[1].as_str() { "hello" => hello()?, + "tinywasm" => tinywasm()?, _ => {} } Ok(()) } +fn tinywasm() -> Result<()> { + const TINYWASM: &[u8] = include_bytes!("./rust/out/tinywasm.wasm"); + let module = Module::parse_bytes(&TINYWASM)?; + let mut store = Store::default(); + + let mut imports = Imports::new(); + imports.define( + "env", + "printi32", + Extern::typed_func(|_: FuncContext<'_>, x: i32| { + println!("{}", x); + Ok(()) + }), + )?; + let instance = module.instantiate(&mut store, Some(imports))?; + + let hello = instance.typed_func::<(), ()>(&mut store, "hello")?; + hello.call(&mut store, ())?; + + Ok(()) +} + fn hello() -> Result<()> { - // const HELLO_WASM: &[u8] = include_bytes!("./rust/out/hello.wasm"); - // let module = Module::parse_bytes(&HELLO_WASM)?; - // let mut store = Store::default(); + const HELLO_WASM: &[u8] = include_bytes!("./rust/out/hello.wasm"); + let module = Module::parse_bytes(&HELLO_WASM)?; + let mut store = Store::default(); - // let mut imports = Imports::new(); - // imports.define( - // "env", - // "printi32", - // Extern::typed_func(|_: FuncContext<'_>, x: i32| { - // println!("{}", x); - // Ok(()) - // }), - // )?; + let mut imports = Imports::new(); + imports.define( + "env", + "printi32", + Extern::typed_func(|_: FuncContext<'_>, x: i32| { + println!("{}", x); + Ok(()) + }), + )?; - // let instance = module.instantiate(&mut store, Some(imports))?; - // let add_and_print = instance.typed_func::<(i32, i32), ()>(&mut store, "add_and_print")?; - // add_and_print.call(&mut store, (1, 2))?; + let instance = module.instantiate(&mut store, Some(imports))?; + let add_and_print = instance.typed_func::<(i32, i32), ()>(&mut store, "add_and_print")?; + add_and_print.call(&mut store, (1, 2))?; Ok(()) } diff --git a/examples/wasm/add.wasm b/examples/wasm/add.wasm Binary files differdeleted file mode 100644 index 92e3432..0000000 --- a/examples/wasm/add.wasm +++ /dev/null diff --git a/examples/wasm/add.wat b/examples/wasm/add.wat deleted file mode 100644 index 4976689..0000000 --- a/examples/wasm/add.wat +++ /dev/null @@ -1,16 +0,0 @@ -(module - (func $add (export "add") (param $a i32) (param $b i32) (result i32) - local.get $a - local.get $b - i32.add) - - (func $sub (export "sub") (param $a i32) (param $b i32) (result i32) - local.get $a - local.get $b - i32.sub) - - (func $add_64 (export "add_64") (param $a i64) (param $b i64) (result i64) - local.get $a - local.get $b - i64.add) -) diff --git a/examples/wasm/call.wat b/examples/wasm/call.wat deleted file mode 100644 index d515e78..0000000 --- a/examples/wasm/call.wat +++ /dev/null @@ -1,42 +0,0 @@ -(module - (func (export "check") (param i32) (result i32) - i64.const 0 ;; Set 0 to the stack - local.get 0 - i32.const 10 - i32.lt_s ;; Check if input is less than 10 - if (param i64) (result i32) ;; If so, - i32.const 1 ;; Set 1 to the stack - return ;; And return immediately - else ;; Otherwise, - i32.const 0 ;; Set 0 to the stack - return ;; And return immediately - end) ;; End of the if/else block - - (func (export "simple_block") (result i32) - (block (result i32) - (i32.const 0) - (i32.const 1) - (i32.add) - ) - ) - - (func (export "checkloop") (result i32) - (block (result i32) - (i32.const 0) - (loop (param i32) - (block (br 2 (i32.const 18))) - (br 0 (i32.const 20)) - ) - (i32.const 19) - ) - ) - - - (func (export "param") (result i32) - (i32.const 1) - (loop (param i32) (result i32) - (i32.const 2) - (i32.add) - ) - ) -)
\ No newline at end of file diff --git a/examples/wasm/global.wat b/examples/wasm/global.wat deleted file mode 100644 index 22bde45..0000000 --- a/examples/wasm/global.wat +++ /dev/null @@ -1 +0,0 @@ -(module (global i32 (i32.const 0)))
\ No newline at end of file diff --git a/examples/wasm/helloworld.wasm b/examples/wasm/helloworld.wasm Binary files differdeleted file mode 100644 index a5c95d0..0000000 --- a/examples/wasm/helloworld.wasm +++ /dev/null diff --git a/examples/wasm/helloworld.wat b/examples/wasm/helloworld.wat deleted file mode 100644 index b74c98f..0000000 --- a/examples/wasm/helloworld.wat +++ /dev/null @@ -1,15 +0,0 @@ -(module - ;; Imports from JavaScript namespace - (import "console" "log" (func $log (param i32 i32))) ;; Import log function - (import "js" "mem" (memory 1)) ;; Import 1 page of memory (54kb) - - ;; Data section of our module - (data (i32.const 0) "Hello World from WebAssembly!") - - ;; Function declaration: Exported as helloWorld(), no arguments - (func (export "helloWorld") - i32.const 0 ;; pass offset 0 to log - i32.const 29 ;; pass length 29 to log (strlen of sample text) - call $log - ) -)
\ No newline at end of file diff --git a/examples/wasm/loop.wat b/examples/wasm/loop.wat deleted file mode 100644 index 0dcd191..0000000 --- a/examples/wasm/loop.wat +++ /dev/null @@ -1,84 +0,0 @@ -(module - (func $loop_test (export "loop_test") (result i32) - (local i32) ;; Local 0: Counter - - ;; Initialize the counter - (local.set 0 (i32.const 0)) - - ;; Loop starts here - (loop $my_loop - ;; Increment the counter - (local.set 0 (i32.add (local.get 0) (i32.const 1))) - - ;; Exit condition: break out of the loop if counter >= 10 - (br_if $my_loop (i32.lt_s (local.get 0) (i32.const 10))) - ) - - ;; Return the counter value - (local.get 0) - ) - - (func $loop_test3 (export "loop_test3") (result i32) - (local i32) ;; Local 0: Counter - - ;; Initialize the counter - (local.set 0 (i32.const 0)) - - ;; Loop starts here - (block $exit_loop ;; Label for exiting the loop - (loop $my_loop - ;; Increment the counter - (local.set 0 (i32.add (local.get 0) (i32.const 1))) - - ;; Prepare an index for br_table - ;; Here, we use the counter, but you could modify this - ;; For simplicity, 0 will continue the loop, any other value will exit - (local.get 0) - (i32.const 10) - (i32.lt_s) - (br_table $my_loop $exit_loop) - ) - ) - - ;; Return the counter value - (local.get 0) - ) - - (func $calculate (export "loop_test2") (result i32) - (local i32) ;; Local 0: Counter for the outer loop - (local i32) ;; Local 1: Counter for the inner loop - (local i32) ;; Local 2: Result variable - - ;; Initialize variables - (local.set 0 (i32.const 0)) ;; Initialize outer loop counter - (local.set 1 (i32.const 0)) ;; Initialize inner loop counter - (local.set 2 (i32.const 0)) ;; Initialize result variable - - (block $outer ;; Outer loop label - (loop $outer_loop - (local.set 1 (i32.const 5)) ;; Reset inner loop counter for each iteration of the outer loop - - (block $inner ;; Inner loop label - (loop $inner_loop - (br_if $inner (i32.eqz (local.get 1))) ;; Break to $inner if inner loop counter is zero - - ;; Computation: Adding product of counters to the result - (local.set 2 (i32.add (local.get 2) (i32.mul (local.get 0) (local.get 1)))) - - ;; Decrement inner loop counter - (local.set 1 (i32.sub (local.get 1) (i32.const 1))) - ) - ) - - ;; Increment outer loop counter - (local.set 0 (i32.add (local.get 0) (i32.const 1))) - - ;; Break condition for outer loop: break if outer loop counter >= 5 - (br_if $outer (i32.ge_s (local.get 0) (i32.const 5))) - ) - ) - - ;; Return the result - (local.get 2) - ) -)
\ No newline at end of file diff --git a/examples/wasm/test.wat b/examples/wasm/test.wat deleted file mode 100644 index 563f382..0000000 --- a/examples/wasm/test.wat +++ /dev/null @@ -1,7 +0,0 @@ -(module - (func (export "test") (result i32) - (i32.const 1) - ;; comment - (return (i32.const 2)) - ) -)
\ No newline at end of file |
