From 398b2af4ea37be98093bdf7ac185a468c62c4aef Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Tue, 12 Mar 2024 18:48:45 +0100 Subject: test: make tests work on more targets (#11) - Tests can now be run on more targets (Fixes #7) - Nightly version has been updated to fix broken builds in some cases (Fixes #8) --- .cargo/config.toml | 2 +- .github/workflows/test.yaml | 103 +++++++++++++++++++---- CHANGELOG.md | 5 +- Cargo.lock | 25 ++++-- Cargo.toml | 4 +- benchmarks/Cargo.toml | 24 ++++++ benchmarks/benches/argon2id.rs | 60 +++++++++++++ benchmarks/benches/fibonacci.rs | 76 +++++++++++++++++ benchmarks/benches/selfhosted.rs | 77 +++++++++++++++++ benchmarks/benches/util/mod.rs | 47 +++++++++++ crates/benchmarks/Cargo.toml | 24 ------ crates/benchmarks/benches/argon2id.rs | 60 ------------- crates/benchmarks/benches/fibonacci.rs | 76 ----------------- crates/benchmarks/benches/selfhosted.rs | 77 ----------------- crates/benchmarks/benches/util/mod.rs | 47 ----------- crates/parser/Cargo.toml | 1 - crates/tinywasm/Cargo.toml | 5 -- crates/tinywasm/src/runtime/stack/block_stack.rs | 2 +- crates/tinywasm/tests/charts/mod.rs | 2 - crates/tinywasm/tests/charts/progress.rs | 77 ----------------- crates/tinywasm/tests/generate-charts.rs | 31 ------- crates/tinywasm/tests/generated/progress-2.0.svg | 25 +++--- crates/tinywasm/tests/generated/progress-mvp.svg | 35 +++----- crates/types/src/instructions.rs | 2 +- crates/wasm-testsuite/Cargo.toml | 2 +- examples/rust/rust-toolchain.toml | 2 - rust-toolchain.toml | 2 +- scripts/Cargo.toml | 8 ++ scripts/src/bin/generate-charts/main.rs | 35 ++++++++ scripts/src/bin/generate-charts/progress.rs | 77 +++++++++++++++++ 30 files changed, 548 insertions(+), 465 deletions(-) create mode 100644 benchmarks/Cargo.toml create mode 100644 benchmarks/benches/argon2id.rs create mode 100644 benchmarks/benches/fibonacci.rs create mode 100644 benchmarks/benches/selfhosted.rs create mode 100644 benchmarks/benches/util/mod.rs delete mode 100644 crates/benchmarks/Cargo.toml delete mode 100644 crates/benchmarks/benches/argon2id.rs delete mode 100644 crates/benchmarks/benches/fibonacci.rs delete mode 100644 crates/benchmarks/benches/selfhosted.rs delete mode 100644 crates/benchmarks/benches/util/mod.rs delete mode 100644 crates/tinywasm/tests/charts/mod.rs delete mode 100644 crates/tinywasm/tests/charts/progress.rs delete mode 100644 crates/tinywasm/tests/generate-charts.rs delete mode 100644 examples/rust/rust-toolchain.toml create mode 100644 scripts/Cargo.toml create mode 100644 scripts/src/bin/generate-charts/main.rs create mode 100644 scripts/src/bin/generate-charts/progress.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index bfe56dd..df52e1c 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,5 +5,5 @@ test-mvp="test --package tinywasm --test test-mvp --release -- --enable " test-2="test --package tinywasm --test test-two --release -- --enable " test-wast="test --package tinywasm --test test-wast -- --enable " test-wast-release="test --package tinywasm --test test-wast --release -- --enable " -generate-charts="test --package tinywasm --test generate-charts -- --enable " +generate-charts="run --package scripts --bin generate-charts --release" benchmark="bench -p benchmarks --bench" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9061eb8..d2b60d1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -7,7 +7,25 @@ on: branches: [main] jobs: + build-wasm: + name: Build wasm + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install Rust toolchain & Binaryen + run: rustup update && rustup target add wasm32-unknown-unknown && sudo apt-get install -y binaryen wabt + - name: Build wasm + run: ./examples/rust/build.sh + - name: Save artifacts + uses: actions/upload-artifact@v4 + with: + name: wasm + path: examples/rust/out + test-std: + needs: build-wasm name: Test with default features on stable Rust runs-on: ubuntu-latest @@ -16,15 +34,14 @@ jobs: with: submodules: true - - name: Install stable Rust toolchain & Binaryen - run: | - rustup update stable - rustup update nightly - rustup target add wasm32-unknown-unknown - sudo apt-get install -y binaryen wabt + - name: Install latest stable Rust toolchain + run: rustup update stable - - name: Build wasm - run: ./examples/rust/build.sh + - name: Load wasm + uses: actions/download-artifact@v4 + with: + name: wasm + path: examples/rust/out - name: Build (stable) run: cargo +stable build --workspace @@ -36,6 +53,7 @@ jobs: run: cargo +stable test-mvp test-no-std: + needs: build-wasm name: Test without default features on nightly Rust runs-on: ubuntu-latest @@ -44,14 +62,14 @@ jobs: with: submodules: true - - name: Install nightly Rust toolchain & Binaryen - run: | - rustup update nightly - rustup target add wasm32-unknown-unknown - sudo apt-get install -y binaryen wabt + - name: Install latest nightly Rust toolchain + run: rustup update nightly - - name: Build wasm - run: ./examples/rust/build.sh + - name: Load wasm + uses: actions/download-artifact@v4 + with: + name: wasm + path: examples/rust/out - name: Build (nightly, no default features) run: cargo +nightly build --workspace --no-default-features @@ -61,3 +79,58 @@ jobs: - name: Run MVP testsuite (nightly) run: cargo +nightly test-mvp + + test-m1: + needs: build-wasm + name: Test on arm64 (Apple M1) + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable Rust toolchain + run: rustup update stable + + - name: Load wasm + uses: actions/download-artifact@v4 + with: + name: wasm + path: examples/rust/out + + - name: Build (stable) + run: cargo +stable build + - name: Run tests (stable) + run: cargo +stable test + - name: Run MVP testsuite + run: cargo +stable test-mvp + + test-armv7: + needs: build-wasm + name: Test on armv7 (32-Bit Raspberry Pi) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Load wasm + uses: actions/download-artifact@v4 + with: + name: wasm + path: examples/rust/out + + - name: Run all tests (for the default workspace members) + uses: houseabsolute/actions-rust-cross@v0.0.12 + with: + command: test + target: armv7-unknown-linux-gnueabihf + toolchain: nightly + + - name: Run MVP testsuite + uses: houseabsolute/actions-rust-cross@v0.0.12 + with: + command: test + args: "-p tinywasm --test test-mvp --release -- --enable" + target: armv7-unknown-linux-gnueabihf + toolchain: nightly diff --git a/CHANGELOG.md b/CHANGELOG.md index 84815c2..e68cd55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Improved documentation and added more tests +- Tests can now be run on more targets +- Nightly version has been updated to fix broken builds in some cases - Enhance support for scripted language bindings by making Imports and Module cloneable +- Add `aarch64-apple-darwin` and `armv7-unknown-linux-gnueabihf` targets to CI ### Removed @@ -75,7 +78,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 **All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.1.0...v0.2.0 -- Support for br_table +- Support for `br_table` - Memory trapping improvements - Implicit function label scopes - else Instructions diff --git a/Cargo.lock b/Cargo.lock index 739a1e2..5369adc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1432,8 +1432,8 @@ dependencies = [ [[package]] name = "pathfinder_simd" -version = "0.5.2" -source = "git+https://github.com/explodingcamera/pathfinder?rev=4ada8c2484f6bdd2a57546f055000c2df8e65eab#4ada8c2484f6bdd2a57546f055000c2df8e65eab" +version = "0.5.3" +source = "git+https://github.com/servo/pathfinder?rev=30419d07660dc11a21e42ef4a7fa329600cff152#30419d07660dc11a21e42ef4a7fa329600cff152" dependencies = [ "rustc_version", ] @@ -1545,9 +1545,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -1796,6 +1796,14 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scripts" +version = "0.0.0" +dependencies = [ + "eyre", + "plotters", +] + [[package]] name = "seahash" version = "4.1.0" @@ -1973,18 +1981,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", @@ -2043,7 +2051,6 @@ dependencies = [ "libm", "log", "owo-colors 4.0.0", - "plotters", "pretty_env_logger", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 17d1404..bb3275c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members=["crates/*"] +members=["crates/*", "benchmarks", "scripts"] default-members=[".", "crates/tinywasm", "crates/types", "crates/parser"] resolver="2" @@ -40,4 +40,4 @@ debug=true [patch.crates-io] # https://github.com/servo/pathfinder/pull/548 & https://github.com/servo/pathfinder/issues/558 -pathfinder_simd={git="https://github.com/explodingcamera/pathfinder", rev="4ada8c2484f6bdd2a57546f055000c2df8e65eab"} +pathfinder_simd={git="https://github.com/servo/pathfinder", rev="30419d07660dc11a21e42ef4a7fa329600cff152"} diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml new file mode 100644 index 0000000..a374713 --- /dev/null +++ b/benchmarks/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name="benchmarks" +publish=false +edition.workspace=true + +[dependencies] +criterion={version="0.5", features=["html_reports"]} +tinywasm={path="../crates/tinywasm", features=["unsafe"]} +wat={version="1.0"} +wasmi={version="0.31", features=["std"]} +wasmer={version="4.2", features=["cranelift", "singlepass"]} +argon2={version="0.5"} + +[[bench]] +name="selfhosted" +harness=false + +[[bench]] +name="fibonacci" +harness=false + +[[bench]] +name="argon2id" +harness=false diff --git a/benchmarks/benches/argon2id.rs b/benchmarks/benches/argon2id.rs new file mode 100644 index 0000000..3046dee --- /dev/null +++ b/benchmarks/benches/argon2id.rs @@ -0,0 +1,60 @@ +mod util; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use util::wasm_to_twasm; + +fn run_tinywasm(twasm: &[u8], params: (i32, i32, i32), name: &str) { + let (mut store, instance) = util::tinywasm(twasm); + let argon2 = instance.exported_func::<(i32, i32, i32), i32>(&store, name).expect("exported_func"); + argon2.call(&mut store, params).expect("call"); +} + +fn run_wasmi(wasm: &[u8], params: (i32, i32, i32), name: &str) { + let (module, mut store, linker) = util::wasmi(wasm); + let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); + let argon2 = instance.get_typed_func::<(i32, i32, i32), i32>(&mut store, name).expect("get_typed_func"); + argon2.call(&mut store, params).expect("call"); +} + +fn run_wasmer(wasm: &[u8], params: (i32, i32, i32), name: &str) { + use wasmer::Value; + let (mut store, instance) = util::wasmer(wasm); + let argon2 = instance.exports.get_function(name).expect("get_function"); + argon2.call(&mut store, &[Value::I32(params.0), Value::I32(params.1), Value::I32(params.2)]).expect("call"); +} + +fn run_native(params: (i32, i32, i32)) { + fn run_native(m_cost: i32, t_cost: i32, p_cost: i32) { + let password = b"password"; + let salt = b"some random salt"; + + let params = argon2::Params::new(m_cost as u32, t_cost as u32, p_cost as u32, None).unwrap(); + let argon = argon2::Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params); + + let mut hash = [0u8; 32]; + argon.hash_password_into(password, salt, &mut hash).unwrap(); + } + run_native(params.0, params.1, params.2) +} + +const ARGON2ID: &[u8] = include_bytes!("../../examples/rust/out/argon2id.wasm"); +fn criterion_benchmark(c: &mut Criterion) { + let twasm = wasm_to_twasm(ARGON2ID); + let params = (1000, 2, 1); + + let mut group = c.benchmark_group("argon2id"); + group.measurement_time(std::time::Duration::from_secs(7)); + group.sample_size(10); + + group.bench_function("native", |b| b.iter(|| run_native(black_box(params)))); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(params), "argon2id"))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi(ARGON2ID, black_box(params), "argon2id"))); + group.bench_function("wasmer", |b| b.iter(|| run_wasmer(ARGON2ID, black_box(params), "argon2id"))); +} + +criterion_group!( + name = benches; + config = Criterion::default().significance_level(0.1); + targets = criterion_benchmark +); + +criterion_main!(benches); diff --git a/benchmarks/benches/fibonacci.rs b/benchmarks/benches/fibonacci.rs new file mode 100644 index 0000000..b391285 --- /dev/null +++ b/benchmarks/benches/fibonacci.rs @@ -0,0 +1,76 @@ +mod util; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use util::wasm_to_twasm; + +fn run_tinywasm(twasm: &[u8], iterations: i32, name: &str) { + let (mut store, instance) = util::tinywasm(twasm); + let fib = instance.exported_func::(&store, name).expect("exported_func"); + fib.call(&mut store, iterations).expect("call"); +} + +fn run_wasmi(wasm: &[u8], iterations: i32, name: &str) { + let (module, mut store, linker) = util::wasmi(wasm); + let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); + let fib = instance.get_typed_func::(&mut store, name).expect("get_typed_func"); + fib.call(&mut store, iterations).expect("call"); +} + +fn run_wasmer(wasm: &[u8], iterations: i32, name: &str) { + use wasmer::*; + let compiler = wasmer::Singlepass::default(); + let mut store = Store::new(compiler); + let import_object = imports! {}; + let module = wasmer::Module::from_binary(&store, wasm).expect("wasmer::Module::from_binary"); + let instance = Instance::new(&mut store, &module, &import_object).expect("Instance::new"); + let fib = instance.exports.get_typed_function::(&store, name).expect("get_function"); + fib.call(&mut store, iterations).expect("call"); +} + +fn run_native(n: i32) -> i32 { + let mut sum = 0; + let mut last = 0; + let mut curr = 1; + for _i in 1..n { + sum = last + curr; + last = curr; + curr = sum; + } + sum +} + +fn run_native_recursive(n: i32) -> i32 { + if n <= 1 { + return n; + } + run_native_recursive(n - 1) + run_native_recursive(n - 2) +} + +const FIBONACCI: &[u8] = include_bytes!("../../examples/rust/out/fibonacci.wasm"); +fn criterion_benchmark(c: &mut Criterion) { + let twasm = wasm_to_twasm(FIBONACCI); + + { + let mut group = c.benchmark_group("fibonacci"); + group.bench_function("native", |b| b.iter(|| run_native(black_box(60)))); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(60), "fibonacci"))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi(FIBONACCI, black_box(60), "fibonacci"))); + group.bench_function("wasmer", |b| b.iter(|| run_wasmer(FIBONACCI, black_box(60), "fibonacci"))); + } + + { + let mut group = c.benchmark_group("fibonacci-recursive"); + group.measurement_time(std::time::Duration::from_secs(5)); + group.bench_function("native", |b| b.iter(|| run_native_recursive(black_box(26)))); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(26), "fibonacci_recursive"))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi(FIBONACCI, black_box(26), "fibonacci_recursive"))); + group.bench_function("wasmer", |b| b.iter(|| run_wasmer(FIBONACCI, black_box(26), "fibonacci_recursive"))); + } +} + +criterion_group!( + name = benches; + config = Criterion::default().significance_level(0.1); + targets = criterion_benchmark +); + +criterion_main!(benches); diff --git a/benchmarks/benches/selfhosted.rs b/benchmarks/benches/selfhosted.rs new file mode 100644 index 0000000..02d44ac --- /dev/null +++ b/benchmarks/benches/selfhosted.rs @@ -0,0 +1,77 @@ +mod util; +use crate::util::twasm_to_module; +use criterion::{criterion_group, criterion_main, Criterion}; + +fn run_native() { + use tinywasm::*; + let module = tinywasm::Module::parse_bytes(include_bytes!("../../examples/rust/out/print.wasm")).expect("parse"); + let mut store = Store::default(); + 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::<(i32, i32), ()>(&store, "add_and_print").expect("exported_func"); + hello.call(&mut store, (2, 3)).expect("call"); +} + +fn run_tinywasm(twasm: &[u8]) { + use tinywasm::*; + let module = twasm_to_module(twasm); + let mut store = Store::default(); + 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::<(), ()>(&store, "hello").expect("exported_func"); + hello.call(&mut store, ()).expect("call"); +} + +fn run_wasmi(wasm: &[u8]) { + use wasmi::*; + let engine = Engine::default(); + let module = wasmi::Module::new(&engine, wasm).expect("wasmi::Module::new"); + let mut store = Store::new(&engine, ()); + let mut linker = >::new(&engine); + linker.define("env", "printi32", Func::wrap(&mut store, |_: Caller<'_, ()>, _: i32| {})).expect("define"); + let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); + let hello = instance.get_typed_func::<(), ()>(&mut store, "hello").expect("get_typed_func"); + hello.call(&mut store, ()).expect("call"); +} + +fn run_wasmer(wasm: &[u8]) { + use wasmer::*; + let engine = wasmer::Engine::default(); + let mut store = Store::default(); + let import_object = imports! { + "env" => { + "printi32" => Function::new_typed(&mut store, |_: i32| {}), + }, + }; + let module = wasmer::Module::from_binary(&engine, wasm).expect("wasmer::Module::from_binary"); + let instance = Instance::new(&mut store, &module, &import_object).expect("Instance::new"); + let hello = instance.exports.get_function("hello").expect("get_function"); + hello.call(&mut store, &[]).expect("call"); +} + +const TINYWASM: &[u8] = include_bytes!("../../examples/rust/out/tinywasm.wasm"); +fn criterion_benchmark(c: &mut Criterion) { + { + let mut group = c.benchmark_group("selfhosted-parse"); + group.bench_function("tinywasm", |b| b.iter(|| util::parse_wasm(TINYWASM))); + } + + { + let twasm = util::wasm_to_twasm(TINYWASM); + let mut group = c.benchmark_group("selfhosted"); + group.bench_function("native", |b| b.iter(run_native)); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi(TINYWASM))); + group.bench_function("wasmer", |b| b.iter(|| run_wasmer(TINYWASM))); + } +} + +criterion_group!( + name = benches; + config = Criterion::default().sample_size(100).measurement_time(std::time::Duration::from_secs(5)).significance_level(0.1); + targets = criterion_benchmark +); + +criterion_main!(benches); diff --git a/benchmarks/benches/util/mod.rs b/benchmarks/benches/util/mod.rs new file mode 100644 index 0000000..f75ce66 --- /dev/null +++ b/benchmarks/benches/util/mod.rs @@ -0,0 +1,47 @@ +#![allow(dead_code)] + +use tinywasm::{parser::Parser, types::TinyWasmModule}; + +pub fn parse_wasm(wasm: &[u8]) -> TinyWasmModule { + let parser = Parser::new(); + parser.parse_module_bytes(wasm).expect("parse_module_bytes") +} + +pub fn wasm_to_twasm(wasm: &[u8]) -> Vec { + let parser = Parser::new(); + let res = parser.parse_module_bytes(wasm).expect("parse_module_bytes"); + res.serialize_twasm().to_vec() +} + +#[inline] +pub fn twasm_to_module(twasm: &[u8]) -> tinywasm::Module { + unsafe { TinyWasmModule::from_twasm_unchecked(twasm) }.into() +} + +pub fn tinywasm(twasm: &[u8]) -> (tinywasm::Store, tinywasm::ModuleInstance) { + use tinywasm::*; + let module = twasm_to_module(twasm); + let mut store = Store::default(); + let imports = Imports::default(); + let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); + (store, instance) +} + +pub fn wasmi(wasm: &[u8]) -> (wasmi::Module, wasmi::Store<()>, wasmi::Linker<()>) { + use wasmi::*; + let engine = Engine::default(); + let module = wasmi::Module::new(&engine, wasm).expect("wasmi::Module::new"); + let store = Store::new(&engine, ()); + let linker = >::new(&engine); + (module, store, linker) +} + +pub fn wasmer(wasm: &[u8]) -> (wasmer::Store, wasmer::Instance) { + use wasmer::*; + let compiler = Singlepass::default(); + let mut store = Store::new(compiler); + let import_object = imports! {}; + let module = Module::new(&store, wasm).expect("wasmer::Module::new"); + let instance = Instance::new(&mut store, &module, &import_object).expect("Instance::new"); + (store, instance) +} diff --git a/crates/benchmarks/Cargo.toml b/crates/benchmarks/Cargo.toml deleted file mode 100644 index b9225c1..0000000 --- a/crates/benchmarks/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name="benchmarks" -publish=false -edition.workspace=true - -[dependencies] -criterion={version="0.5", features=["html_reports"]} -tinywasm={path="../../crates/tinywasm", features=["unsafe"]} -wat={version="1.0"} -wasmi={version="0.31", features=["std"]} -wasmer={version="4.2", features=["cranelift", "singlepass"]} -argon2={version="0.5"} - -[[bench]] -name="selfhosted" -harness=false - -[[bench]] -name="fibonacci" -harness=false - -[[bench]] -name="argon2id" -harness=false diff --git a/crates/benchmarks/benches/argon2id.rs b/crates/benchmarks/benches/argon2id.rs deleted file mode 100644 index 7c1ffc5..0000000 --- a/crates/benchmarks/benches/argon2id.rs +++ /dev/null @@ -1,60 +0,0 @@ -mod util; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use util::wasm_to_twasm; - -fn run_tinywasm(twasm: &[u8], params: (i32, i32, i32), name: &str) { - let (mut store, instance) = util::tinywasm(twasm); - let argon2 = instance.exported_func::<(i32, i32, i32), i32>(&store, name).expect("exported_func"); - argon2.call(&mut store, params).expect("call"); -} - -fn run_wasmi(wasm: &[u8], params: (i32, i32, i32), name: &str) { - let (module, mut store, linker) = util::wasmi(wasm); - let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); - let argon2 = instance.get_typed_func::<(i32, i32, i32), i32>(&mut store, name).expect("get_typed_func"); - argon2.call(&mut store, params).expect("call"); -} - -fn run_wasmer(wasm: &[u8], params: (i32, i32, i32), name: &str) { - use wasmer::Value; - let (mut store, instance) = util::wasmer(wasm); - let argon2 = instance.exports.get_function(name).expect("get_function"); - argon2.call(&mut store, &[Value::I32(params.0), Value::I32(params.1), Value::I32(params.2)]).expect("call"); -} - -fn run_native(params: (i32, i32, i32)) { - fn run_native(m_cost: i32, t_cost: i32, p_cost: i32) { - let password = b"password"; - let salt = b"some random salt"; - - let params = argon2::Params::new(m_cost as u32, t_cost as u32, p_cost as u32, None).unwrap(); - let argon = argon2::Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params); - - let mut hash = [0u8; 32]; - argon.hash_password_into(password, salt, &mut hash).unwrap(); - } - run_native(params.0, params.1, params.2) -} - -const ARGON2ID: &[u8] = include_bytes!("../../../examples/rust/out/argon2id.wasm"); -fn criterion_benchmark(c: &mut Criterion) { - let twasm = wasm_to_twasm(ARGON2ID); - let params = (1000, 2, 1); - - let mut group = c.benchmark_group("argon2id"); - group.measurement_time(std::time::Duration::from_secs(7)); - group.sample_size(10); - - group.bench_function("native", |b| b.iter(|| run_native(black_box(params)))); - group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(params), "argon2id"))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi(ARGON2ID, black_box(params), "argon2id"))); - group.bench_function("wasmer", |b| b.iter(|| run_wasmer(ARGON2ID, black_box(params), "argon2id"))); -} - -criterion_group!( - name = benches; - config = Criterion::default().significance_level(0.1); - targets = criterion_benchmark -); - -criterion_main!(benches); diff --git a/crates/benchmarks/benches/fibonacci.rs b/crates/benchmarks/benches/fibonacci.rs deleted file mode 100644 index 8a4dab2..0000000 --- a/crates/benchmarks/benches/fibonacci.rs +++ /dev/null @@ -1,76 +0,0 @@ -mod util; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use util::wasm_to_twasm; - -fn run_tinywasm(twasm: &[u8], iterations: i32, name: &str) { - let (mut store, instance) = util::tinywasm(twasm); - let fib = instance.exported_func::(&store, name).expect("exported_func"); - fib.call(&mut store, iterations).expect("call"); -} - -fn run_wasmi(wasm: &[u8], iterations: i32, name: &str) { - let (module, mut store, linker) = util::wasmi(wasm); - let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); - let fib = instance.get_typed_func::(&mut store, name).expect("get_typed_func"); - fib.call(&mut store, iterations).expect("call"); -} - -fn run_wasmer(wasm: &[u8], iterations: i32, name: &str) { - use wasmer::*; - let compiler = wasmer::Singlepass::default(); - let mut store = Store::new(compiler); - let import_object = imports! {}; - let module = wasmer::Module::from_binary(&store, wasm).expect("wasmer::Module::from_binary"); - let instance = Instance::new(&mut store, &module, &import_object).expect("Instance::new"); - let fib = instance.exports.get_typed_function::(&store, name).expect("get_function"); - fib.call(&mut store, iterations).expect("call"); -} - -fn run_native(n: i32) -> i32 { - let mut sum = 0; - let mut last = 0; - let mut curr = 1; - for _i in 1..n { - sum = last + curr; - last = curr; - curr = sum; - } - sum -} - -fn run_native_recursive(n: i32) -> i32 { - if n <= 1 { - return n; - } - run_native_recursive(n - 1) + run_native_recursive(n - 2) -} - -const FIBONACCI: &[u8] = include_bytes!("../../../examples/rust/out/fibonacci.wasm"); -fn criterion_benchmark(c: &mut Criterion) { - let twasm = wasm_to_twasm(FIBONACCI); - - { - let mut group = c.benchmark_group("fibonacci"); - group.bench_function("native", |b| b.iter(|| run_native(black_box(60)))); - group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(60), "fibonacci"))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi(FIBONACCI, black_box(60), "fibonacci"))); - group.bench_function("wasmer", |b| b.iter(|| run_wasmer(FIBONACCI, black_box(60), "fibonacci"))); - } - - { - let mut group = c.benchmark_group("fibonacci-recursive"); - group.measurement_time(std::time::Duration::from_secs(5)); - group.bench_function("native", |b| b.iter(|| run_native_recursive(black_box(26)))); - group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(26), "fibonacci_recursive"))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi(FIBONACCI, black_box(26), "fibonacci_recursive"))); - group.bench_function("wasmer", |b| b.iter(|| run_wasmer(FIBONACCI, black_box(26), "fibonacci_recursive"))); - } -} - -criterion_group!( - name = benches; - config = Criterion::default().significance_level(0.1); - targets = criterion_benchmark -); - -criterion_main!(benches); diff --git a/crates/benchmarks/benches/selfhosted.rs b/crates/benchmarks/benches/selfhosted.rs deleted file mode 100644 index 94dfdce..0000000 --- a/crates/benchmarks/benches/selfhosted.rs +++ /dev/null @@ -1,77 +0,0 @@ -mod util; -use crate::util::twasm_to_module; -use criterion::{criterion_group, criterion_main, Criterion}; - -fn run_native() { - use tinywasm::*; - let module = tinywasm::Module::parse_bytes(include_bytes!("../../../examples/rust/out/print.wasm")).expect("parse"); - let mut store = Store::default(); - 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::<(i32, i32), ()>(&store, "add_and_print").expect("exported_func"); - hello.call(&mut store, (2, 3)).expect("call"); -} - -fn run_tinywasm(twasm: &[u8]) { - use tinywasm::*; - let module = twasm_to_module(twasm); - let mut store = Store::default(); - 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::<(), ()>(&store, "hello").expect("exported_func"); - hello.call(&mut store, ()).expect("call"); -} - -fn run_wasmi(wasm: &[u8]) { - use wasmi::*; - let engine = Engine::default(); - let module = wasmi::Module::new(&engine, wasm).expect("wasmi::Module::new"); - let mut store = Store::new(&engine, ()); - let mut linker = >::new(&engine); - linker.define("env", "printi32", Func::wrap(&mut store, |_: Caller<'_, ()>, _: i32| {})).expect("define"); - let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); - let hello = instance.get_typed_func::<(), ()>(&mut store, "hello").expect("get_typed_func"); - hello.call(&mut store, ()).expect("call"); -} - -fn run_wasmer(wasm: &[u8]) { - use wasmer::*; - let engine = wasmer::Engine::default(); - let mut store = Store::default(); - let import_object = imports! { - "env" => { - "printi32" => Function::new_typed(&mut store, |_: i32| {}), - }, - }; - let module = wasmer::Module::from_binary(&engine, wasm).expect("wasmer::Module::from_binary"); - let instance = Instance::new(&mut store, &module, &import_object).expect("Instance::new"); - let hello = instance.exports.get_function("hello").expect("get_function"); - hello.call(&mut store, &[]).expect("call"); -} - -const TINYWASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm"); -fn criterion_benchmark(c: &mut Criterion) { - { - let mut group = c.benchmark_group("selfhosted-parse"); - group.bench_function("tinywasm", |b| b.iter(|| util::parse_wasm(TINYWASM))); - } - - { - let twasm = util::wasm_to_twasm(TINYWASM); - let mut group = c.benchmark_group("selfhosted"); - group.bench_function("native", |b| b.iter(run_native)); - group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi(TINYWASM))); - group.bench_function("wasmer", |b| b.iter(|| run_wasmer(TINYWASM))); - } -} - -criterion_group!( - name = benches; - config = Criterion::default().sample_size(100).measurement_time(std::time::Duration::from_secs(5)).significance_level(0.1); - targets = criterion_benchmark -); - -criterion_main!(benches); diff --git a/crates/benchmarks/benches/util/mod.rs b/crates/benchmarks/benches/util/mod.rs deleted file mode 100644 index 0df2a52..0000000 --- a/crates/benchmarks/benches/util/mod.rs +++ /dev/null @@ -1,47 +0,0 @@ -#![allow(dead_code)] - -use tinywasm::{self, parser::Parser, types::TinyWasmModule}; - -pub fn parse_wasm(wasm: &[u8]) -> TinyWasmModule { - let parser = Parser::new(); - parser.parse_module_bytes(wasm).expect("parse_module_bytes") -} - -pub fn wasm_to_twasm(wasm: &[u8]) -> Vec { - let parser = Parser::new(); - let res = parser.parse_module_bytes(wasm).expect("parse_module_bytes"); - res.serialize_twasm().to_vec() -} - -#[inline] -pub fn twasm_to_module(twasm: &[u8]) -> tinywasm::Module { - unsafe { TinyWasmModule::from_twasm_unchecked(twasm) }.into() -} - -pub fn tinywasm(twasm: &[u8]) -> (tinywasm::Store, tinywasm::ModuleInstance) { - use tinywasm::*; - let module = twasm_to_module(twasm); - let mut store = Store::default(); - let imports = Imports::default(); - let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); - (store, instance) -} - -pub fn wasmi(wasm: &[u8]) -> (wasmi::Module, wasmi::Store<()>, wasmi::Linker<()>) { - use wasmi::*; - let engine = Engine::default(); - let module = wasmi::Module::new(&engine, wasm).expect("wasmi::Module::new"); - let store = Store::new(&engine, ()); - let linker = >::new(&engine); - (module, store, linker) -} - -pub fn wasmer(wasm: &[u8]) -> (wasmer::Store, wasmer::Instance) { - use wasmer::*; - let compiler = Singlepass::default(); - let mut store = Store::new(compiler); - let import_object = imports! {}; - let module = Module::new(&store, wasm).expect("wasmer::Module::new"); - let instance = Instance::new(&mut store, &module, &import_object).expect("Instance::new"); - (store, instance) -} diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index b21c0ac..197023d 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -17,4 +17,3 @@ tinywasm-types={version="0.5.0", path="../types", default-features=false} default=["std", "logging"] logging=["log"] std=["tinywasm-types/std"] - diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index dc0f525..3f42ca0 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -25,7 +25,6 @@ owo-colors={version="4.0"} eyre={version="0.6"} serde_json={version="1.0"} serde={version="1.0", features=["derive"]} -plotters={version="0.3"} pretty_env_logger="0.5" [features] @@ -36,10 +35,6 @@ parser=["tinywasm-parser"] unsafe=["tinywasm-types/unsafe"] archive=["tinywasm-types/archive"] -[[test]] -name="generate-charts" -harness=false - [[test]] name="test-mvp" harness=false diff --git a/crates/tinywasm/src/runtime/stack/block_stack.rs b/crates/tinywasm/src/runtime/stack/block_stack.rs index 4fe7690..edaf2d1 100644 --- a/crates/tinywasm/src/runtime/stack/block_stack.rs +++ b/crates/tinywasm/src/runtime/stack/block_stack.rs @@ -3,7 +3,7 @@ use alloc::vec::Vec; use tinywasm_types::BlockArgs; #[derive(Debug, Clone, Default)] -pub(crate) struct BlockStack(Vec); // TODO: maybe Box<[LabelFrame]> by analyzing the lable count when parsing the module? +pub(crate) struct BlockStack(Vec); // TODO: maybe Box<[LabelFrame]> by analyzing the label count when parsing the module? impl BlockStack { #[inline] diff --git a/crates/tinywasm/tests/charts/mod.rs b/crates/tinywasm/tests/charts/mod.rs deleted file mode 100644 index fba287b..0000000 --- a/crates/tinywasm/tests/charts/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod progress; -pub use progress::create_progress_chart; diff --git a/crates/tinywasm/tests/charts/progress.rs b/crates/tinywasm/tests/charts/progress.rs deleted file mode 100644 index 1ecc09c..0000000 --- a/crates/tinywasm/tests/charts/progress.rs +++ /dev/null @@ -1,77 +0,0 @@ -use eyre::Result; -use plotters::prelude::*; -use std::fs::File; -use std::io::{self, BufRead}; -use std::path::Path; - -const FONT: &str = "Victor Mono"; - -pub fn create_progress_chart(name: &str, csv_path: &Path, output_path: &Path) -> Result<()> { - let file = File::open(csv_path)?; - let reader = io::BufReader::new(file); - - let mut max_tests = 0; - let mut data: Vec = Vec::new(); - let mut versions: Vec = Vec::new(); - - for line in reader.lines() { - let line = line?; - let parts: Vec<&str> = line.split(',').collect(); - - if parts.len() > 3 { - let version = format!("v{}", parts[0]); - let passed: u32 = parts[1].parse()?; - let failed: u32 = parts[2].parse()?; - let total = failed + passed; - - if total > max_tests { - max_tests = total; - } - - versions.push(version); - data.push(passed); - } - } - - let root_area = SVGBackend::new(output_path, (1000, 400)).into_drawing_area(); - root_area.fill(&WHITE)?; - - let mut chart = ChartBuilder::on(&root_area) - .x_label_area_size(45) - .y_label_area_size(70) - .margin(10) - .margin_top(20) - .caption(name, (FONT, 30.0, FontStyle::Bold)) - .build_cartesian_2d((0..(versions.len() - 1) as u32).into_segmented(), 0..max_tests)?; - - chart - .configure_mesh() - .light_line_style(TRANSPARENT) - .bold_line_style(BLACK.mix(0.3)) - .max_light_lines(10) - .disable_x_mesh() - .y_desc("Tests Passed") - .y_label_style((FONT, 15)) - .x_desc("TinyWasm Version") - .x_labels((versions.len()).min(4)) - .x_label_style((FONT, 15)) - .x_label_formatter(&|x| { - let SegmentValue::CenterOf(value) = x else { - return "".to_string(); - }; - let v = versions.get(*value as usize).unwrap_or(&"".to_string()).to_string(); - format!("{} ({})", v, data[*value as usize]) - }) - .axis_desc_style((FONT, 15, FontStyle::Bold)) - .draw()?; - - chart.draw_series( - Histogram::vertical(&chart) - .style(BLUE.mix(0.5).filled()) - .data(data.iter().enumerate().map(|(x, y)| (x as u32, *y))), - )?; - - root_area.present()?; - - Ok(()) -} diff --git a/crates/tinywasm/tests/generate-charts.rs b/crates/tinywasm/tests/generate-charts.rs deleted file mode 100644 index ec48703..0000000 --- a/crates/tinywasm/tests/generate-charts.rs +++ /dev/null @@ -1,31 +0,0 @@ -mod charts; -use eyre::Result; - -fn main() -> Result<()> { - generate_charts() -} - -fn generate_charts() -> Result<()> { - let args = std::env::args().collect::>(); - if args.len() < 2 || args[1] != "--enable" { - return Ok(()); - } - - charts::create_progress_chart( - "WebAssembly 1.0 Test Suite", - std::path::Path::new("./tests/generated/mvp.csv"), - std::path::Path::new("./tests/generated/progress-mvp.svg"), - )?; - - println!("created progress chart: ./tests/generated/progress-mvp.svg"); - - charts::create_progress_chart( - "WebAssembly 2.0 Test Suite", - std::path::Path::new("./tests/generated/2.0.csv"), - std::path::Path::new("./tests/generated/progress-2.0.svg"), - )?; - - println!("created progress chart: ./tests/generated/progress-2.0.svg"); - - Ok(()) -} diff --git a/crates/tinywasm/tests/generated/progress-2.0.svg b/crates/tinywasm/tests/generated/progress-2.0.svg index 6424367..32eee9e 100644 --- a/crates/tinywasm/tests/generated/progress-2.0.svg +++ b/crates/tinywasm/tests/generated/progress-2.0.svg @@ -41,19 +41,24 @@ TinyWasm Version - + v0.3.0 (26722) - - + + v0.4.0 (27549) - - -v0.4.1 (27552) + + +v0.4.1 (27551) - - - - + + +v0.5.0 (27551) + + + + + + diff --git a/crates/tinywasm/tests/generated/progress-mvp.svg b/crates/tinywasm/tests/generated/progress-mvp.svg index 2a26dd5..dcb1838 100644 --- a/crates/tinywasm/tests/generated/progress-mvp.svg +++ b/crates/tinywasm/tests/generated/progress-mvp.svg @@ -36,27 +36,20 @@ TinyWasm Version - + v0.0.4 (9258) - - -v0.1.0 (17630) - - - -v0.3.0 (20254) - - - -v0.4.1 (20257) - - - - - - - - - + + +v0.4.0 (20254) + + + + + + + + + + diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 9923116..c932704 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -72,7 +72,7 @@ pub enum ConstInstruction { /// Wasm Bytecode can map to multiple of these instructions. /// /// # Differences to the spec -/// * `br_table` stores the jump lables in the following `br_label` instructions to keep this enum small. +/// * `br_table` stores the jump labels in the following `br_label` instructions to keep this enum small. /// * Lables/Blocks: we store the label end offset in the instruction itself and /// have seperate EndBlockFrame and EndFunc instructions to mark the end of a block or function. /// This makes it easier to implement the label stack iteratively. diff --git a/crates/wasm-testsuite/Cargo.toml b/crates/wasm-testsuite/Cargo.toml index 97c1f58..2e2fc21 100644 --- a/crates/wasm-testsuite/Cargo.toml +++ b/crates/wasm-testsuite/Cargo.toml @@ -15,4 +15,4 @@ path="lib.rs" independent=true [dependencies] -rust-embed={version="8.1.0", features=["include-exclude"]} +rust-embed={version="8.3", features=["include-exclude"]} diff --git a/examples/rust/rust-toolchain.toml b/examples/rust/rust-toolchain.toml deleted file mode 100644 index 6c22ba5..0000000 --- a/examples/rust/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel="nightly-2024-02-11" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 6c22ba5..7d876a7 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel="nightly-2024-02-11" +channel="nightly-2024-03-11" diff --git a/scripts/Cargo.toml b/scripts/Cargo.toml new file mode 100644 index 0000000..5217729 --- /dev/null +++ b/scripts/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name="scripts" +publish=false +edition.workspace=true + +[dependencies] +plotters={version="0.3"} +eyre={version="0.6"} diff --git a/scripts/src/bin/generate-charts/main.rs b/scripts/src/bin/generate-charts/main.rs new file mode 100644 index 0000000..1206e5f --- /dev/null +++ b/scripts/src/bin/generate-charts/main.rs @@ -0,0 +1,35 @@ +mod progress; +use std::{path::PathBuf, str::FromStr}; + +use eyre::Result; + +fn main() -> Result<()> { + generate_charts() +} + +fn generate_charts() -> Result<()> { + let results_dir = PathBuf::from_str("./crates/tinywasm/tests/generated")?; + + // check if the folder exists + if !results_dir.exists() { + return Err(eyre::eyre!( + "This script should be run from the root of the project, and the test results should be generated first." + )); + } + + progress::create_progress_chart( + "WebAssembly 1.0 Test Suite", + &results_dir.join("mvp.csv"), + &results_dir.join("progress-mvp.svg"), + )?; + println!("created progress chart: {}", results_dir.join("progress-mvp.svg").display()); + + progress::create_progress_chart( + "WebAssembly 2.0 Test Suite", + &results_dir.join("2.0.csv"), + &results_dir.join("progress-2.0.svg"), + )?; + println!("created progress chart: {}", results_dir.join("progress-2.0.svg").display()); + + Ok(()) +} diff --git a/scripts/src/bin/generate-charts/progress.rs b/scripts/src/bin/generate-charts/progress.rs new file mode 100644 index 0000000..1ecc09c --- /dev/null +++ b/scripts/src/bin/generate-charts/progress.rs @@ -0,0 +1,77 @@ +use eyre::Result; +use plotters::prelude::*; +use std::fs::File; +use std::io::{self, BufRead}; +use std::path::Path; + +const FONT: &str = "Victor Mono"; + +pub fn create_progress_chart(name: &str, csv_path: &Path, output_path: &Path) -> Result<()> { + let file = File::open(csv_path)?; + let reader = io::BufReader::new(file); + + let mut max_tests = 0; + let mut data: Vec = Vec::new(); + let mut versions: Vec = Vec::new(); + + for line in reader.lines() { + let line = line?; + let parts: Vec<&str> = line.split(',').collect(); + + if parts.len() > 3 { + let version = format!("v{}", parts[0]); + let passed: u32 = parts[1].parse()?; + let failed: u32 = parts[2].parse()?; + let total = failed + passed; + + if total > max_tests { + max_tests = total; + } + + versions.push(version); + data.push(passed); + } + } + + let root_area = SVGBackend::new(output_path, (1000, 400)).into_drawing_area(); + root_area.fill(&WHITE)?; + + let mut chart = ChartBuilder::on(&root_area) + .x_label_area_size(45) + .y_label_area_size(70) + .margin(10) + .margin_top(20) + .caption(name, (FONT, 30.0, FontStyle::Bold)) + .build_cartesian_2d((0..(versions.len() - 1) as u32).into_segmented(), 0..max_tests)?; + + chart + .configure_mesh() + .light_line_style(TRANSPARENT) + .bold_line_style(BLACK.mix(0.3)) + .max_light_lines(10) + .disable_x_mesh() + .y_desc("Tests Passed") + .y_label_style((FONT, 15)) + .x_desc("TinyWasm Version") + .x_labels((versions.len()).min(4)) + .x_label_style((FONT, 15)) + .x_label_formatter(&|x| { + let SegmentValue::CenterOf(value) = x else { + return "".to_string(); + }; + let v = versions.get(*value as usize).unwrap_or(&"".to_string()).to_string(); + format!("{} ({})", v, data[*value as usize]) + }) + .axis_desc_style((FONT, 15, FontStyle::Bold)) + .draw()?; + + chart.draw_series( + Histogram::vertical(&chart) + .style(BLUE.mix(0.5).filled()) + .data(data.iter().enumerate().map(|(x, y)| (x as u32, *y))), + )?; + + root_area.present()?; + + Ok(()) +} -- cgit v1.3.1