From f3d890a36d6299dbd70377ce57b0bd9d61de5c1b Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Fri, 26 Jan 2024 12:03:59 +0100 Subject: [PATCH] docs: working selfhosted example Signed-off-by: Henry Gressmann --- .gitignore | 2 +- Cargo.lock | 7 +++ Cargo.toml | 9 +++- README.md | 13 +----- crates/tinywasm/src/error.rs | 40 +++++++++++++++- examples/README.md | 22 +++++++++ examples/rust/Cargo.toml | 7 +-- examples/rust/build.sh | 18 ++++++++ examples/rust/src/tinywasm.rs | 51 +++++++++------------ examples/wasm-rust.rs | 58 ++++++++++++++++------- examples/wasm/add.wasm | Bin 65 -> 0 bytes examples/wasm/add.wat | 16 ------- examples/wasm/call.wat | 42 ----------------- examples/wasm/global.wat | 1 - examples/wasm/helloworld.wasm | Bin 115 -> 0 bytes examples/wasm/helloworld.wat | 15 ------ examples/wasm/loop.wat | 84 ---------------------------------- examples/wasm/test.wat | 7 --- 18 files changed, 159 insertions(+), 233 deletions(-) create mode 100644 examples/README.md create mode 100755 examples/rust/build.sh delete mode 100644 examples/wasm/add.wasm delete mode 100644 examples/wasm/add.wat delete mode 100644 examples/wasm/call.wat delete mode 100644 examples/wasm/global.wat delete mode 100644 examples/wasm/helloworld.wasm delete mode 100644 examples/wasm/helloworld.wat delete mode 100644 examples/wasm/loop.wat delete mode 100644 examples/wasm/test.wat diff --git a/.gitignore b/.gitignore index 7bb669d..f5dcc83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /target notes.md -examples/tinywasm.wat +examples/rust/out/* examples/wast/* diff --git a/Cargo.lock b/Cargo.lock index 84cd8cc..fbd82db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1045,6 +1045,13 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rust-wasm-examples" +version = "0.0.0" +dependencies = [ + "tinywasm", +] + [[package]] name = "rustc-demangle" version = "0.1.23" diff --git a/Cargo.toml b/Cargo.toml index 2be4dcc..b3ed635 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 3197cf1..5055889 100644 --- a/README.md +++ b/README.md @@ -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. - - +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 +``` + +Where `` 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; 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 mut imports = tinywasm::Imports::new(); - // imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| 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 module = tinywasm::Module::parse_bytes(include_bytes!("../../wasm/hello.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.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 "); 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(); - - // 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))?; + 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 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 deleted file mode 100644 index 92e343278f5bf783a31d603872c2af25cb118d1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65 zcmZQbEY4+QU|?Y6WlCVGuV<`JV5+NQtYc8Nnv1M1CsG(CJc;Rf=uiT O3JeO2S=wl>C*{u#TwFt3E GKgTaC&l%AG 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