diff --git a/Cargo.lock b/Cargo.lock index be046bf..4fa0d16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,12 +26,62 @@ dependencies = [ "memchr", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.98" @@ -161,7 +211,7 @@ dependencies = [ "rustix", "slab", "tracing", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -317,6 +367,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cfg-if" version = "1.0.0" @@ -332,6 +388,33 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "4.5.39" @@ -348,6 +431,7 @@ version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" dependencies = [ + "anstream", "anstyle", "clap_lex", "strsim", @@ -371,6 +455,12 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -380,12 +470,73 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "displaydoc" version = "0.2.5" @@ -416,7 +567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -619,6 +770,17 @@ dependencies = [ "scroll", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.15.3" @@ -755,6 +917,32 @@ dependencies = [ "serde", ] +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -877,6 +1065,18 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "parking" version = "2.2.1" @@ -924,6 +1124,34 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "polling" version = "3.8.0" @@ -936,7 +1164,7 @@ dependencies = [ "pin-project-lite", "rustix", "tracing", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -972,6 +1200,26 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "regex" version = "1.11.1" @@ -1023,7 +1271,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1038,6 +1286,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scroll" version = "0.12.0" @@ -1185,7 +1442,7 @@ dependencies = [ "getrandom", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1229,6 +1486,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tokio" version = "1.45.1" @@ -1339,7 +1606,7 @@ dependencies = [ "clap", "glob", "heck", - "itertools", + "itertools 0.14.0", "once_cell", "paste", "regex", @@ -1351,6 +1618,7 @@ dependencies = [ "uniffi-example-futures", "uniffi-example-geometry", "uniffi-example-rondpoint", + "uniffi-fixture-benchmarks", "uniffi-fixture-coverall", "uniffi-fixture-ext-types", "uniffi-fixture-futures", @@ -1410,6 +1678,18 @@ dependencies = [ "uniffi", ] +[[package]] +name = "uniffi-fixture-benchmarks" +version = "0.22.0" +source = "git+https://github.com/mozilla/uniffi-rs.git?tag=v0.31.0#309762f55db3f0548194a9ceba3027fa64b18a93" +dependencies = [ + "clap", + "criterion", + "regex", + "thiserror", + "uniffi", +] + [[package]] name = "uniffi-fixture-coverall" version = "0.22.0" @@ -1746,12 +2026,28 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "value-bag" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" @@ -1859,6 +2155,21 @@ dependencies = [ "nom", ] +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.59.0" @@ -1868,6 +2179,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1986,6 +2306,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index 126becb..3709f3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ uniffi-example-custom-types = { git = "https://github.com/mozilla/uniffi-rs.git" uniffi-example-futures = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.31.0" } uniffi-example-geometry = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.31.0" } uniffi-example-rondpoint = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.31.0" } +uniffi-fixture-benchmarks = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.31.0" } uniffi-fixture-coverall = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.31.0" } uniffi-fixture-ext-types = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.31.0" } uniffi-fixture-futures = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.31.0" } @@ -57,3 +58,12 @@ uniffi-fixture-rename = { git = "https://github.com/mozilla/uniffi-rs.git", tag uniffi-fixture-time = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.31.0" } uniffi-fixture-trait-methods = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.31.0" } uniffi_testing = { git = "https://github.com/mozilla/uniffi-rs.git", tag = "v0.31.0" } + +[[bench]] +name = "benchmarks" +harness = false + +# Ensure fixture cdylibs are optimized when benchmarking. +# uniffi_testing builds them via `cargo test --no-run`, which uses the test profile. +[profile.test.package."uniffi-fixture-benchmarks"] +opt-level = 3 diff --git a/benches/README.md b/benches/README.md new file mode 100644 index 0000000..4aebbe3 --- /dev/null +++ b/benches/README.md @@ -0,0 +1,74 @@ +# Benchmarks + +Criterion-based benchmarks measuring FFI call overhead for the generated Java bindings. Uses the same `uniffi-fixture-benchmarks` fixture and benchmark structure as upstream [uniffi-rs](https://github.com/mozilla/uniffi-rs/tree/main/fixtures/benchmarks), making results directly comparable with Kotlin, Python, and Swift. + +## Prerequisites + +Rust toolchain, JDK 21+, and JNA on the `CLASSPATH` — all provided by `nix develop`. + +## Running + +```bash +cargo bench # full suite +cargo bench -- call-only # filter by name +cargo bench -- --save-baseline before # save a Criterion baseline +cargo bench -- --load-baseline before # compare against a saved baseline +``` + +## How It Works + +`cargo bench` runs `benches/benchmarks.rs` which: + +1. Builds the `uniffi-fixture-benchmarks` cdylib (opt-level 3) +2. Generates Java bindings and packages them into a jar +3. Compiles `benches/bindings/RunBenchmarks.java` +4. Runs the Java process, which calls `Benchmarks.runBenchmarks("java", callback)` + +Criterion runs inside the Rust fixture library. The Java side implements `TestCallbackInterface` and provides timing for function-call benchmarks via `runTest()` (using `System.nanoTime()`). Callback benchmarks are timed directly by Criterion on the Rust side. + +## Benchmark Groups + +- **function-calls** — Java calling Rust functions across 12 type categories +- **callbacks** — Rust calling Java callback methods across the same 12 categories + +## Results + +HTML reports are written to `target/criterion/`. Raw data persists across runs for regression detection. + +### Pre-results + +A run of the benchmarks (and comparison to upstream) on March 26, 2026 with an M4 Max 2024 Macbook Pro, 36GB memory. There was an upstream error in Python's `nested-data` bench at this time. All calls are in microseconds unless noted. + +#### Function Calls (foreign code calling Rust) + +| Test Case | Java | Kotlin | Python | Swift | +|--------------------|----------|----------|----------|----------| +| call-only | 2.1 | 2.8 | 810 ns | 172 ns | +| primitives | 1.8 | 3.0 | 1.4 | 195 ns | +| strings | 14.7 | 16.8 | 9.2 | 973 ns | +| large-strings | 15.9 | 21.5 | 12.5 | 1.3 | +| records | 14.5 | 16.6 | 18.9 | 2.6 | +| enums | 12.6 | 16.7 | 14.6 | 2.1 | +| vecs | 15.6 | 17.2 | 18.3 | 4.4 | +| hash-maps | 12.3 | 16.9 | 23.4 | 6.3 | +| interfaces | 8.6 | 13.8 | 4.3 | 396 ns | +| trait-interfaces | 9.2 | 12.8 | 4.4 | 461 ns | +| nested-data | 13.5 | 17.9 | --- | 20.6 | +| errors | 4.7 | 7.4 | 3.2 | 642 ns | + +#### Callbacks (Rust calling foreign code) + +| Test Case | Java | Kotlin | Python | Swift | +|--------------------|----------|----------|----------|----------| +| call-only | 2.6 | 3.2 | 477 ns | 121 ns | +| primitives | 4.3 | 3.9 | 793 ns | 166 ns | +| strings | 13.5 | 18.0 | 8.0 | 811 ns | +| large-strings | 18.2 | 22.6 | 10.7 | 1.6 | +| records | 16.6 | 17.5 | 14.0 | 2.8 | +| enums | 17.0 | 17.5 | 11.0 | 2.4 | +| vecs | 16.9 | 17.9 | 18.1 | 4.8 | +| hash-maps | 12.7 | 18.1 | 21.4 | 7.1 | +| interfaces | 7.8 | 35.6 | 4.1 | 429 ns | +| trait-interfaces | 11.1 | 27.2 | 4.3 | 528 ns | +| nested-data | 21.8 | 16.8 | --- | 24.1 | +| errors | 10.4 | 8.9 | 4.3 | 566 ns | diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs new file mode 100644 index 0000000..05f3963 --- /dev/null +++ b/benches/benchmarks.rs @@ -0,0 +1,165 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Criterion benchmark harness for uniffi-bindgen-java +//! +//! Matches the upstream uniffi-rs benchmark style: builds the fixture cdylib, +//! generates Java bindings, compiles a Java benchmark runner, and executes it. +//! Criterion runs inside the Rust fixture library, driven by `runBenchmarks()`. +//! +//! Usage: +//! cargo bench +//! cargo bench -- --filter call-only # filter benchmarks +//! cargo bench -- --save-baseline name # save Criterion baseline + +use anyhow::{Context, Result, bail}; +use camino::Utf8PathBuf; +use std::env; +use std::env::consts::ARCH; +use std::fs; +use std::path::PathBuf; +use std::process::Command; +use uniffi_bindgen::{BindgenLoader, BindgenPaths}; +use uniffi_bindgen_java::{GenerateOptions, generate}; +use uniffi_testing::UniFFITestHelper; + +fn main() -> Result<()> { + let project_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let tmp_dir = PathBuf::from(std::env!("CARGO_TARGET_TMPDIR")).join("benchmarks"); + + // Clean and recreate the temp directory + if tmp_dir.exists() { + fs::remove_dir_all(&tmp_dir)?; + } + fs::create_dir_all(&tmp_dir)?; + + // Build the benchmarks fixture cdylib + println!("Building benchmarks fixture..."); + let test_helper = UniFFITestHelper::new("uniffi-fixture-benchmarks")?; + let cdylib_path = test_helper.cdylib_path()?; + println!(" cdylib: {cdylib_path}"); + + // Generate Java bindings + println!("Generating Java bindings..."); + let out_dir = Utf8PathBuf::from(tmp_dir.to_string_lossy().to_string()); + + let mut paths = BindgenPaths::default(); + paths.add_cargo_metadata_layer(false)?; + let loader = BindgenLoader::new(paths); + + generate( + &loader, + &GenerateOptions { + source: cdylib_path.clone(), + out_dir: out_dir.clone(), + format: false, + crate_filter: None, + }, + )?; + + // Set up JNA native library path: copy cdylib into {os}-{arch}/ inside a staging dir + let jna_resource_folder = if cdylib_path.extension().unwrap() == "dylib" { + format!("darwin-{}", ARCH).replace('_', "-") + } else { + format!("linux-{}", ARCH).replace('_', "-") + }; + let staging_dir = tmp_dir.join("staging"); + let native_resource_dir = staging_dir.join(&jna_resource_folder); + fs::create_dir_all(&native_resource_dir)?; + let cdylib_dest = native_resource_dir.join(cdylib_path.file_name().unwrap()); + fs::copy(cdylib_path.as_std_path(), &cdylib_dest)?; + + // Compile generated bindings into a jar + println!("Compiling Java bindings..."); + let java_sources: Vec<_> = glob::glob(&format!("{}/**/*.java", out_dir))? + .flatten() + .map(|p| p.to_string_lossy().to_string()) + .collect(); + + let classpath = calc_classpath(vec![]); + let status = Command::new("javac") + .arg("-d") + .arg(&staging_dir) + .arg("-classpath") + .arg(&classpath) + .args(&java_sources) + .spawn() + .context("Failed to spawn `javac` to compile the bindings")? + .wait() + .context("Failed to wait for `javac`")?; + if !status.success() { + bail!("javac failed when compiling the generated bindings") + } + + let jar_file = tmp_dir.join("benchmarks.jar"); + let jar_status = Command::new("jar") + .current_dir(&tmp_dir) + .arg("cf") + .arg(jar_file.file_name().unwrap()) + .arg("-C") + .arg(&staging_dir) + .arg(".") + .spawn() + .context("Failed to spawn `jar`")? + .wait() + .context("Failed to wait for `jar`")?; + if !jar_status.success() { + bail!("jar failed when packaging the bindings") + } + + // Compile the benchmark runner script + println!("Compiling benchmark runner..."); + let runner_src = project_root.join("benches/bindings/RunBenchmarks.java"); + let runner_classpath = calc_classpath(vec![jar_file.to_string_lossy().to_string()]); + let status = Command::new("javac") + .arg("-classpath") + .arg(&runner_classpath) + .arg("-d") + .arg(&tmp_dir) + .arg(&runner_src) + .spawn() + .context("Failed to spawn `javac` to compile the benchmark runner")? + .wait() + .context("Failed to wait for `javac`")?; + if !status.success() { + bail!("javac failed when compiling the benchmark runner") + } + + // Run the benchmark. The Java process calls Benchmarks.runBenchmarks() which + // drives Criterion internally. We pass CLI args through via "--" so that the + // fixture's Args::parse_for_run_benchmarks() can pick them up. + println!("Running benchmarks..."); + let run_classpath = calc_classpath(vec![ + jar_file.to_string_lossy().to_string(), + tmp_dir.to_string_lossy().to_string(), + ]); + + // Collect args after "--" to pass through to the Java process + let pass_through_args: Vec = env::args().skip_while(|a| a != "--").collect(); + + let mut cmd = Command::new("java"); + cmd.arg("-classpath") + .arg(&run_classpath) + .arg("RunBenchmarks") + .args(&pass_through_args); + + let status = cmd + .spawn() + .context("Failed to spawn `java` to run benchmarks")? + .wait() + .context("Failed to wait for `java`")?; + if !status.success() { + bail!("Benchmark run failed") + } + + Ok(()) +} + +fn calc_classpath(extra_paths: Vec) -> String { + extra_paths + .into_iter() + .chain(env::var("CLASSPATH")) + .collect::>() + .join(":") +} diff --git a/benches/bindings/RunBenchmarks.java b/benches/bindings/RunBenchmarks.java new file mode 100644 index 0000000..8f0b29a --- /dev/null +++ b/benches/bindings/RunBenchmarks.java @@ -0,0 +1,190 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import uniffi.benchmarks.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +class TestData { + static final String testLargeString1 = "a".repeat(2048); + static final String testLargeString2 = "b".repeat(1500); + static final TestRecord testRec1 = new TestRecord(-1, 1L, 1.5); + static final TestRecord testRec2 = new TestRecord(-2, 2L, 4.5); + static final TestEnum testEnum1 = new TestEnum.One(-1, 0L); + static final TestEnum testEnum2 = new TestEnum.Two(1.5); + static final int[] testVec1 = new int[]{0, 1}; + static final int[] testVec2 = new int[]{2, 4, 6}; + static final Map testMap1 = Map.of(0, 1, 1, 2); + static final Map testMap2 = Map.of(2, 4); + static final TestInterface testInterface = new TestInterface(); + static final TestInterface testInterface2 = new TestInterface(); + static final TestTraitInterface testTraitInterface = Benchmarks.makeTestTraitInterface(); + static final TestTraitInterface testTraitInterface2 = Benchmarks.makeTestTraitInterface(); + static final NestedData testNestedData1 = new NestedData( + List.of(new TestRecord(-1, 1L, 1.5)), + List.of(List.of("one", "two"), List.of("three")), + Map.of( + "one", new TestEnum.One(-1, 1L), + "two", new TestEnum.Two(0.5) + ) + ); + static final NestedData testNestedData2 = new NestedData( + List.of(new TestRecord(-2, 2L, 4.5)), + List.of(List.of("four", "five")), + Map.of("two", new TestEnum.Two(-0.5)) + ); +} + +class TestCallbackObj implements TestCallbackInterface { + @Override + public void callOnly() { + } + + @Override + public double primitives(byte a, int b) { + return (double) a + (double) b; + } + + @Override + public String strings(String a, String b) { + return a + b; + } + + @Override + public String largeStrings(String a, String b) { + return a + b; + } + + @Override + public TestRecord records(TestRecord a, TestRecord b) { + return new TestRecord(a.a() + b.a(), a.b() + b.b(), a.c() + b.c()); + } + + @Override + public TestEnum enums(TestEnum a, TestEnum b) { + double aSum = switch (a) { + case TestEnum.One one -> (double) one.a() + (double) one.b(); + case TestEnum.Two two -> two.c(); + }; + double bSum = switch (b) { + case TestEnum.One one -> (double) one.a() + (double) one.b(); + case TestEnum.Two two -> two.c(); + }; + return new TestEnum.Two(aSum + bSum); + } + + @Override + public int[] vecs(int[] a, int[] b) { + int[] result = new int[a.length + b.length]; + System.arraycopy(a, 0, result, 0, a.length); + System.arraycopy(b, 0, result, a.length, b.length); + return result; + } + + @Override + public Map hashMaps(Map a, Map b) { + var result = new HashMap<>(a); + result.putAll(b); + return result; + } + + @Override + public TestInterface interfaces(TestInterface a, TestInterface b) { + return a.equals(b) ? a : b; + } + + @Override + public TestTraitInterface traitInterfaces(TestTraitInterface a, TestTraitInterface b) { + return a.equals(b) ? a : b; + } + + @Override + public NestedData nestedData(NestedData a, NestedData b) { + return a.equals(b) ? a : b; + } + + @Override + public int errors() throws TestException { + throw new TestException.Two(); + } + + @Override + public long runTest(TestCase testCase, long count) { + long start = System.nanoTime(); + switch (testCase) { + case CALL_ONLY -> { + for (long i = 0; i < count; i++) { + Benchmarks.testCaseCallOnly(); + } + } + case PRIMITIVES -> { + for (long i = 0; i < count; i++) { + Benchmarks.testCasePrimitives((byte) 0, 1); + } + } + case STRINGS -> { + for (long i = 0; i < count; i++) { + Benchmarks.testCaseStrings("a", "b"); + } + } + case LARGE_STRINGS -> { + for (long i = 0; i < count; i++) { + Benchmarks.testCaseLargeStrings(TestData.testLargeString1, TestData.testLargeString2); + } + } + case RECORDS -> { + for (long i = 0; i < count; i++) { + Benchmarks.testCaseRecords(TestData.testRec1, TestData.testRec2); + } + } + case ENUMS -> { + for (long i = 0; i < count; i++) { + Benchmarks.testCaseEnums(TestData.testEnum1, TestData.testEnum2); + } + } + case VECS -> { + for (long i = 0; i < count; i++) { + Benchmarks.testCaseVecs(TestData.testVec1, TestData.testVec2); + } + } + case HASHMAPS -> { + for (long i = 0; i < count; i++) { + Benchmarks.testCaseHashmaps(TestData.testMap1, TestData.testMap2); + } + } + case INTERFACES -> { + for (long i = 0; i < count; i++) { + Benchmarks.testCaseInterfaces(TestData.testInterface, TestData.testInterface2); + } + } + case TRAIT_INTERFACES -> { + for (long i = 0; i < count; i++) { + Benchmarks.testCaseTraitInterfaces(TestData.testTraitInterface, TestData.testTraitInterface2); + } + } + case NESTED_DATA -> { + for (long i = 0; i < count; i++) { + Benchmarks.testCaseNestedData(TestData.testNestedData1, TestData.testNestedData2); + } + } + case ERRORS -> { + for (long i = 0; i < count; i++) { + try { + Benchmarks.testCaseErrors(); + } catch (TestException e) { + // expected + } + } + } + } + return System.nanoTime() - start; + } +} + +public class RunBenchmarks { + public static void main(String[] args) { + Benchmarks.runBenchmarks("java", new TestCallbackObj()); + } +}