diff --git a/.gitignore b/.gitignore index 1935339..5b78c22 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,9 @@ dist/ *.tmp # ports/webcrypto conformance runner output (conformance/run.ts) ports/webcrypto/conformance/results-*.jsonl + +# bench/boundary: local artifacts (the recipe rebuilds them) +bench/boundary/deltic-embedder.local.mjs +bench/boundary/generated/ +bench/boundary/node_modules/ +bench/boundary/guest/target/ diff --git a/bench/boundary/README.md b/bench/boundary/README.md new file mode 100644 index 0000000..6b60e4c --- /dev/null +++ b/bench/boundary/README.md @@ -0,0 +1,80 @@ +# bench/boundary — the host-boundary microbench + +Calls-per-second across the host import boundary, per ABI shape — the +instrument behind [#17](https://github.com/lann/deltic/issues/17)'s +jco-vs-deltic baseline, [#54](https://github.com/lann/deltic/issues/54)'s +lift-throughput finding, and [#8](https://github.com/lann/deltic/issues/8)'s +cost ledger. The design goal is attribution, not realism: the guest is a +tight loop over echo-shaped imports whose host bodies are trivial, so +what the clock sees is lift/lower + dispatch + (for async shapes) the +suspension machinery — and both stacks run on **the same engine** (plain +`node` runs the deltic callback ABI with no flag; the jco lane and +deltic's jspi mode share `--experimental-wasm-jspi`), so V8/GC/JIT +variables cancel. + +```sh +just bench-boundary # deltic lanes: node callback+jspi, deno +just bench-boundary with-jco # + the incumbent jco lane (npm ci + transpile on first use) +``` + +The deltic lanes measure the CURRENT TREE: the recipe builds the local +embedder bundle (`tools/release-bundle/build.ts`) and the local +translator shim. Numbers are box-relative — compare lanes within one +run, or the same lane across commits on one box, never absolute values +across machines. + +## Shapes + +| export | import it loops | boundary shape | +| --- | --- | --- | +| `send` | `ping: async func(list) -> u32` | UDP-send-shaped: payload guest→host | +| `recv` | `fetch: async func(u32) -> list` | UDP-receive-shaped: payload host→guest | +| `send-sync` | `ping-sync: func(list) -> u32` | the sync-lowered control | + +Host settlement `mode`: `immediate` (a plain return value — the fast +path) and `microtask` (an async host fn, i.e. an already-resolved +promise — the wakeup-shaped path a real receive takes). Payload sizes 0 +(pure call overhead) and 1200 B (QUIC-ish MTU). Medians of 5 timed +export calls after a warmup call; each export call runs `iters` +boundary crossings. + +## Baseline (2026-08-10, linux-arm64 dev box, Node 24.18 / Deno 2.9.5, guest wit-bindgen 0.60) + +``` +shape mode size deltic-node-callback deltic-node-jspi deltic-deno-callback jco-node-jspi +send immediate 0 1,079,807/s 1,016,348/s 1,168,392/s 336/s +send immediate 1200 680,210/s 637,144/s 722,588/s 336/s +send microtask 0 541,613/s 250,349/s 526,868/s 338/s +send microtask 1200 396,974/s 223,360/s 399,989/s 339/s +recv immediate 0 1,182,824/s 1,111,585/s 1,381,300/s 345/s +recv immediate 1200 18,546/s 18,213/s 21,706/s 375/s +recv microtask 0 550,893/s 300,230/s 604,611/s 338/s +recv microtask 1200 18,160/s 17,364/s 20,366/s 490/s +send-sync immediate 0 1,604,379/s 1,057,590/s 1,440,209/s 338,332/s +send-sync immediate 1200 759,075/s 591,627/s 922,990/s 300,576/s +send-sync microtask 0 1,545,879/s 1,234,868/s 1,359,083/s 337,477/s +send-sync microtask 1200 629,432/s 623,742/s 826,725/s 310,692/s +``` + +What the baseline says: + +- **Async import round-trips**: deltic's callback ABI sustains 0.4–1.2 M + crossings/s; jco's async path costs ~3 ms per call flat + (timer-quantized — its sync path is healthy at ~300 k/s, so the cost + is the async task loop, the same machinery behind lann/jco#11 and + polymorph-iroh's 5 ms polling workaround). For the UDP direct path + (#4) this is the difference between "boundary is free" and "boundary + is the bottleneck". +- **#54**: host→guest `list` lift runs ~45 ns/byte (~22 MB/s + ceiling, flat across sizes — a per-element copy), while guest→host + lower is bulk (~6.4 GB/s at 16 KiB). The `recv @ 1200` rows are the + regression sentinel for the fix. +- **jspi vs callback** (same runtime, same engine): parity on + immediate-settled paths, ~2× behind on deferred (microtask) paths — + the suspend/resume cost, recorded for #8. + +The jco lane pins the family's own toolchain (the lann/jco all-fixes +transpile + preview2-shim release tarballs, the vendored +`jco-transpile.mjs` wrapper, and polymorph-test's `bindImports` for the +WASI spellings — the exact stack the consumer repos' jco legs run). It +exists as the incumbent baseline and retires with the jco era. diff --git a/bench/boundary/driver-deltic.mjs b/bench/boundary/driver-deltic.mjs new file mode 100644 index 0000000..b0add0b --- /dev/null +++ b/bench/boundary/driver-deltic.mjs @@ -0,0 +1,54 @@ +// The deltic lane of the boundary microbench: runs under BOTH plain +// `node` (callback ABI, no engine flag) and `deno run -A`. +// +// node driver-deltic.mjs \ +// [reps] [jspi] +// +// The bundle is the embedder surface — the LOCAL tree's +// (tools/release-bundle/build.ts output) for tracking this repo, or a +// pinned release asset for cross-version comparison. shape: send | recv +// | send-sync; mode: immediate | microtask (see host.mjs); jspi selects +// jspi-mode suspension (needs --experimental-wasm-jspi under node). +// Emits one JSON line: { lane, shape, mode, size, iters, medianMs, +// callsPerSec }. +import { makeHost } from "./host.mjs"; + +const isDeno = typeof Deno !== "undefined"; +const readFile = isDeno + ? (p) => Deno.readFile(p) + : async (p) => new Uint8Array(await (await import("node:fs/promises")).readFile(p)); +const argv = isDeno ? Deno.args : process.argv.slice(2); +const [bundlePath, translatorPath, shape, mode, itersS, sizeS, repsS, jspiFlag] = argv; +const iters = Number(itersS), size = Number(sizeS), reps = Number(repsS ?? 5); + +const cwd = isDeno ? Deno.cwd() : process.cwd(); +const deltic = await import(new URL(bundlePath, `file://${cwd}/`).href); +const translator = await deltic.Translator.create(await readFile(translatorPath)); +const componentBytes = await readFile( + new URL("./guest/target/wasm32-wasip2/release/boundary_bench_guest.wasm", import.meta.url).pathname, +); +const { plan, adapters } = translator.translate(componentBytes); + +const imports = { + ...deltic.wasiShims(), + "bench:boundary/host@0.1.0": makeHost(mode), +}; +const inst = await deltic.instantiate( + { plan, componentBytes, adapters }, + imports, + jspiFlag === "jspi" ? { jspi: true } : { jspi: false }, +); +const fn = { send: inst.exports.send, recv: inst.exports.recv, "send-sync": inst.exports.sendSync }[shape]; + +await fn(Math.min(iters, 1000), size); // warmup +const times = []; +for (let r = 0; r < reps; r++) { + const t0 = performance.now(); + await fn(iters, size); + times.push(performance.now() - t0); +} +times.sort((a, b) => a - b); +const median = times[Math.floor(times.length / 2)]; +const engine = isDeno ? "deno" : "node"; +const lane = `deltic-${engine}-${jspiFlag === "jspi" ? "jspi" : "callback"}`; +console.log(JSON.stringify({ lane, shape, mode, size, iters, medianMs: median, callsPerSec: Math.round(iters / (median / 1000)) })); diff --git a/bench/boundary/driver-jco.mjs b/bench/boundary/driver-jco.mjs new file mode 100644 index 0000000..29bda33 --- /dev/null +++ b/bench/boundary/driver-jco.mjs @@ -0,0 +1,39 @@ +// The jco lane of the boundary microbench (the incumbent baseline; this +// lane retires with the jco era). Needs the npm tree (`npm ci` here) and +// a transpile (`node jco-transpile.mjs transpile ...` — the recipe does +// both): +// +// node --experimental-wasm-jspi driver-jco.mjs [reps] +// +// Emits the same JSON line shape as driver-deltic.mjs. +import { readFile } from "node:fs/promises"; +import { instantiate } from "./generated/bench.js"; +import { bindImports } from "@polymorph/component-test-js/imports"; +import { cli, clocks, io, random, filesystem } from "@bytecodealliance/preview2-shim"; +import { makeHost } from "./host.mjs"; + +const [shape, mode, itersS, sizeS, repsS] = process.argv.slice(2); +const iters = Number(itersS), size = Number(sizeS), reps = Number(repsS ?? 5); + +const modules = new Map(); +for (const name of ["bench.core.wasm", "bench.core2.wasm", "bench.core3.wasm"]) { + modules.set(name, await WebAssembly.compile(await readFile(new URL(`./generated/${name}`, import.meta.url)))); +} +const imports = bindImports({ + wasi: { cli, clocks, io, random, filesystem }, + env: [], + sut: { "bench:boundary/host": makeHost(mode) }, +}); +const inst = await instantiate((n) => modules.get(n), imports); +const fn = { send: inst.send, recv: inst.recv, "send-sync": inst.sendSync }[shape]; + +await fn(Math.min(iters, 1000), size); // warmup +const times = []; +for (let r = 0; r < reps; r++) { + const t0 = performance.now(); + await fn(iters, size); + times.push(performance.now() - t0); +} +times.sort((a, b) => a - b); +const median = times[Math.floor(times.length / 2)]; +console.log(JSON.stringify({ lane: "jco-node-jspi", shape, mode, size, iters, medianMs: median, callsPerSec: Math.round(iters / (median / 1000)) })); diff --git a/bench/boundary/guest/Cargo.lock b/bench/boundary/guest/Cargo.lock new file mode 100644 index 0000000..b923ef2 --- /dev/null +++ b/bench/boundary/guest/Cargo.lock @@ -0,0 +1,337 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "boundary-bench-guest" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "macro-string" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a9dbbfc75d2688ed057456ce8a3ee3f48d12eec09229f560f3643b9f275653" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasm-encoder" +version = "0.254.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09480d646178e5fdd12bb06e812d0af9a3a191dbc9cd697fdc86687beade7393" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.254.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b01df5f3b4ca7881e843f3bc0fb8a3905d79c68692250dcb8e33e698705ccdb6" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.254.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5769a29f799fbab136aaf65b4fe5384cd7d93fe6fc9ba0dcb6c8382a1f16e27" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a301904d6657d6364c758d869e5389d05d393b16d5b65db60b4f03cbe71bb80d" +dependencies = [ + "bitflags", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48521bb96e56cbb9e031ad306c80dc20e89e69e49ada02781ee06f60fbcb545f" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df3fe9b9a0066f82fd0c2f07f79d0ffc0f85c53fa5f998516d8722712479042" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.119", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d689c4bc9d6af067c651cfba444766343c9a4bdef15fad1e2efab95c0c277af0" +dependencies = [ + "anyhow", + "macro-string", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.119", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.254.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0e65bb94c369b3c4741ce3d1d2704b1fec93db7c540df0e521a097e7ceeb5be" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.254.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1655131e4f7d3f0cb141f6eca71315ca40eff0f3d4de7cff0a82bacedd8c89b4" +dependencies = [ + "anyhow", + "hashbrown", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-ident", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/bench/boundary/guest/Cargo.toml b/bench/boundary/guest/Cargo.toml new file mode 100644 index 0000000..5e4bfe5 --- /dev/null +++ b/bench/boundary/guest/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "boundary-bench-guest" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = { version = "=0.60.0", features = ["async"] } + +[profile.release] +opt-level = "s" + +# Standalone on purpose (not a workspace member): the bench guest builds +# for wasm32-wasip2 with its own lock, like the fixture guests. +[workspace] diff --git a/bench/boundary/guest/src/lib.rs b/bench/boundary/guest/src/lib.rs new file mode 100644 index 0000000..862a576 --- /dev/null +++ b/bench/boundary/guest/src/lib.rs @@ -0,0 +1,45 @@ +//! The boundary microbench guest: tight loops over the host imports so +//! the host can time calls-per-second for each boundary shape. + +mod bindings { + wit_bindgen::generate!({ + path: "wit", + world: "bench", + generate_all, + }); +} + +use bindings::bench::boundary::host; + +struct Component; + +impl bindings::Guest for Component { + async fn send(iters: u32, size: u32) -> u64 { + let payload = vec![0xa5u8; size as usize]; + let mut acc = 0u64; + for _ in 0..iters { + acc = acc.wrapping_add(host::ping(payload.clone()).await as u64); + } + acc + } + + async fn recv(iters: u32, size: u32) -> u64 { + let mut acc = 0u64; + for _ in 0..iters { + let got = host::fetch(size).await; + acc = acc.wrapping_add(got.len() as u64); + } + acc + } + + async fn send_sync(iters: u32, size: u32) -> u64 { + let payload = vec![0xa5u8; size as usize]; + let mut acc = 0u64; + for _ in 0..iters { + acc = acc.wrapping_add(host::ping_sync(&payload) as u64); + } + acc + } +} + +bindings::export!(Component with_types_in bindings); diff --git a/bench/boundary/guest/wit/bench.wit b/bench/boundary/guest/wit/bench.wit new file mode 100644 index 0000000..1448e99 --- /dev/null +++ b/bench/boundary/guest/wit/bench.wit @@ -0,0 +1,22 @@ +package bench:boundary@0.1.0; + +/// The host side of the boundary microbench: echo-shaped imports whose +/// cost IS the measurement (the host bodies are trivial). +interface host { + /// send-shaped: a payload crosses guest -> host, a small scalar returns. + ping: async func(payload: list) -> u32; + /// receive-shaped: a small scalar crosses, a payload returns host -> guest. + fetch: async func(size: u32) -> list; + /// send-shaped, sync-lowered (the non-async boundary for comparison). + ping-sync: func(payload: list) -> u32; +} + +world bench { + import host; + /// Each export loops its import `iters` times with a `size`-byte + /// payload and folds the results (defeating dead-code elimination); + /// the host times the whole export call. + export send: async func(iters: u32, size: u32) -> u64; + export recv: async func(iters: u32, size: u32) -> u64; + export send-sync: async func(iters: u32, size: u32) -> u64; +} diff --git a/bench/boundary/host.mjs b/bench/boundary/host.mjs new file mode 100644 index 0000000..d44414c --- /dev/null +++ b/bench/boundary/host.mjs @@ -0,0 +1,28 @@ +// The bench host module: echo-shaped bodies whose cost is negligible, in +// two settlement modes — "immediate" (a plain value: the send fast path) +// and "microtask" (an already-resolved promise: the wakeup-shaped path a +// real UDP receive takes). +const buffers = new Map(); +function bufferOf(size) { + let b = buffers.get(size); + if (b === undefined) { + b = new Uint8Array(size).fill(0x5a); + buffers.set(size, b); + } + return b; +} + +export function makeHost(mode) { + if (mode === "immediate") { + return { + ping: (payload) => payload.length >>> 0, + fetch: (size) => bufferOf(size), + pingSync: (payload) => payload.length >>> 0, + }; + } + return { + ping: async (payload) => payload.length >>> 0, + fetch: async (size) => bufferOf(size), + pingSync: (payload) => payload.length >>> 0, + }; +} diff --git a/bench/boundary/jco-transpile.mjs b/bench/boundary/jco-transpile.mjs new file mode 100644 index 0000000..ee27fa7 --- /dev/null +++ b/bench/boundary/jco-transpile.mjs @@ -0,0 +1,87 @@ +#!/usr/bin/env node +// Vendored from polymorph-test (js/jco-transpile.mjs at 2436bb9, deleted +// there when that repo went jco-free): a thin CLI over +// @bytecodealliance/jco-transpile, covering the two jco +// commands the conformance consumers use: `transpile` (component to ES +// module) and `types` (host-side type definitions for a WIT world). +// jco-transpile is the transpilation half of jco, published without +// the componentization toolchain (componentize-js, weval) that the +// full jco CLI drags in and nothing here runs. +// +// The library is resolved from the invoking package's node_modules — +// each consumer pins its own toolchain version — so this bin must run +// with the consumer's package directory as the working directory, +// which is how package.json scripts invoke it. +// +// The option spellings match the jco CLI's, and the library applies +// the same defaults the CLI did (name derivation, the wasi-shim map +// entries, output-path prefixing), so the generated trees are +// bit-identical to `jco transpile` / `jco types` output for these +// invocations. + +import { createRequire } from "node:module"; +import { join } from "node:path"; +import { pathToFileURL } from "node:url"; +import { parseArgs } from "node:util"; + +const require = createRequire(join(process.cwd(), "package.json")); +const { transpile, generateHostTypes, writeFiles } = await import( + pathToFileURL(require.resolve("@bytecodealliance/jco-transpile")).href +); + +const [command, path, ...rest] = process.argv.slice(2); +const { values } = parseArgs({ + args: rest, + options: { + name: { type: "string" }, + "async-mode": { type: "string" }, + instantiation: { type: "string", short: "I" }, + map: { type: "string", multiple: true }, + "world-name": { type: "string" }, + "out-dir": { type: "string", short: "o" }, + // Never answer an async-lowered import with a bare RETURNED status; + // required for componentize-js guests, whose lowering does not + // implement the returned-immediately case (see the option's doc in + // jco-transpile). + "no-eager-subtask-return": { type: "boolean" }, + // `types` only: WIT `@unstable` gates to enable, like `jco types + // --feature` (repeatable). + feature: { type: "string", multiple: true }, + }, +}); + +switch (command) { + case "transpile": { + const map = Object.fromEntries( + (values.map ?? []).map((entry) => { + const eq = entry.indexOf("="); + if (eq === -1) { + throw new Error(`--map entry has no '=': ${entry}`); + } + return [entry.slice(0, eq), entry.slice(eq + 1)]; + }), + ); + const { files } = await transpile(path, { + name: values.name, + asyncMode: values["async-mode"], + instantiation: values.instantiation, + map, + outDir: values["out-dir"], + noEagerSubtaskReturn: values["no-eager-subtask-return"], + }); + await writeFiles(files); + break; + } + case "types": { + const files = await generateHostTypes(path, { + worldName: values["world-name"], + asyncMode: values["async-mode"], + outDir: values["out-dir"], + features: values.feature, + }); + await writeFiles(files); + break; + } + default: + throw new Error(`unknown command: ${command} (expected transpile or types)`); +} diff --git a/bench/boundary/package-lock.json b/bench/boundary/package-lock.json new file mode 100644 index 0000000..8e08fe0 --- /dev/null +++ b/bench/boundary/package-lock.json @@ -0,0 +1,526 @@ +{ + "name": "boundary-bench", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "boundary-bench", + "dependencies": { + "@bytecodealliance/jco-transpile": "https://github.com/lann/jco/releases/download/all-fixes-dbad4d7/bytecodealliance-jco-transpile-0.6.3-gdbad4d7.tgz", + "@bytecodealliance/preview2-shim": "https://github.com/lann/jco/releases/download/all-fixes-dbad4d7/bytecodealliance-preview2-shim-0.20.1-gdbad4d7.tgz", + "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#4ce2a7a45d479a22bc8eb2696dd9ebe0b2a3516b" + } + }, + "node_modules/@bytecodealliance/jco-transpile": { + "version": "0.6.3-gdbad4d7", + "resolved": "https://github.com/lann/jco/releases/download/all-fixes-dbad4d7/bytecodealliance-jco-transpile-0.6.3-gdbad4d7.tgz", + "integrity": "sha512-6/I8xS2Xr3ggPVdyl8Rj4QeYv71tYm/KYpiXIUaR9pX+uxMScKT8jSKvCKdWI3IdvMgzz2UgeAn+BhYYLTD3DQ==", + "license": "(Apache-2.0 WITH LLVM-exception)", + "dependencies": { + "@bytecodealliance/preview2-shim": "^0.20.1", + "@bytecodealliance/preview3-shim": "^0.2.2", + "binaryen": "^130.0.0", + "oxc-minify": "^0.136.0" + } + }, + "node_modules/@bytecodealliance/jco-transpile/node_modules/@bytecodealliance/preview2-shim": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.20.1.tgz", + "integrity": "sha512-gvQP6DWfeQIYKHBb+nPykku1DRtfXQjM685+e75YYr8ZF2ZxisE4+nWrpwf84+NaWJRfuL5HvoLKQFbup1iwkQ==", + "license": "(Apache-2.0 WITH LLVM-exception)" + }, + "node_modules/@bytecodealliance/preview2-shim": { + "version": "0.20.1-gdbad4d7", + "resolved": "https://github.com/lann/jco/releases/download/all-fixes-dbad4d7/bytecodealliance-preview2-shim-0.20.1-gdbad4d7.tgz", + "integrity": "sha512-pCKKbbJrAboirUFOmAryjH/qEDmVB7uLIkzVXziEgwptPxv37GkyvTWZ6UMSZuqP1amExw2wyFEdalo6R2lI+g==", + "license": "(Apache-2.0 WITH LLVM-exception)" + }, + "node_modules/@bytecodealliance/preview3-shim": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@bytecodealliance/preview3-shim/-/preview3-shim-0.2.2.tgz", + "integrity": "sha512-9z4tjc+qixUhyeYo3ycoT4cZ9yIlUfkowJxGoKeMfbRd8zT3uvIr1YNfXyU0I93GPu+gdaTVsr8iMr55utEJSA==", + "license": "(Apache-2.0 WITH LLVM-exception)", + "dependencies": { + "@bytecodealliance/preview2-shim": "^0.19.0" + } + }, + "node_modules/@bytecodealliance/preview3-shim/node_modules/@bytecodealliance/preview2-shim": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.19.0.tgz", + "integrity": "sha512-OSUlcM62fXLuzYP0DjBn/SwjKMZ/y/2PMXZ8v5cRiiFcroauntlPRl/oCnx0xYjYiqdYvE0PY50VCNutll0xOw==", + "license": "(Apache-2.0 WITH LLVM-exception)" + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + } + }, + "node_modules/@oxc-minify/binding-android-arm-eabi": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-android-arm-eabi/-/binding-android-arm-eabi-0.136.0.tgz", + "integrity": "sha512-gk1jzky1vLeOcK7w9Ib2RT/UooZ7O21s8YSeXCsy4tzsmguKb3Cbsv/Ra2fKhMp4mKeKCzvC1Xa41OfSBWfWdQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-android-arm64": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-android-arm64/-/binding-android-arm64-0.136.0.tgz", + "integrity": "sha512-5GIYanqud/ohXEcg2dsdn0+eSKXqclw5qlRUkvSM1sKEa4ZQ7hqOk8XPJBNPh3LXXw+Qw85B6jkmOFLKsCHp5w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-darwin-arm64": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-darwin-arm64/-/binding-darwin-arm64-0.136.0.tgz", + "integrity": "sha512-QAMpHKXWOoMCTenSZjQjVT5YmMooujlCKAf1T3PHFl2LuaA6ukQFXba0bgSLgAxRrAeX/itkwi4lzVQXtYmnRg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-darwin-x64": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-darwin-x64/-/binding-darwin-x64-0.136.0.tgz", + "integrity": "sha512-rDnoPj8hztiU8C1VNKuzujh7SgTJlgiUvUCiWQjTnVSLsABsLW9ZWd1l1Ji0VN3UW39wGpptgvahIWTrHnGH/A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-freebsd-x64": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-freebsd-x64/-/binding-freebsd-x64-0.136.0.tgz", + "integrity": "sha512-9fGSgP7uyPLCTjMx9z3AUV4UjnfOVoGTH5JzamLegPwGLJ97Gr1jht1437CRk8ZNabGmxQR6qNn3e+Tox2lswg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-arm-gnueabihf": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.136.0.tgz", + "integrity": "sha512-k0QvZS+x6ROI4O5Ikqn9dDU2dmkEJ46Yh6baZJI/eUk/a1k1GOs9/vyXItLZrKlwYrSgdEx2BFXbKgyd1NyRPw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-arm-musleabihf": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.136.0.tgz", + "integrity": "sha512-iRFxUc8ubotVfuyq3e4LPGbHrjvWd1/B3499a55GueMLc1EDJ0fHzNXJiCx2eMQGfzHtHodjmvrJ2BSvSXBNhQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-arm64-gnu": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.136.0.tgz", + "integrity": "sha512-hbKxNCXJsULrb6EyG5SumzJKuPfP8Y0UQ/TElRgXVUhyyPf6cBPlF8HMq+MAylF98ACBzV8jBp2Mm62lJyTo+w==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-arm64-musl": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.136.0.tgz", + "integrity": "sha512-T+u/CGKW0UXemJeVux4ix6rK+ug3bQCUEbu5hFlX9K61KQNd44rOcrM15mDK99L7USaXwmiwHeIkEDvmmyDMSA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-ppc64-gnu": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.136.0.tgz", + "integrity": "sha512-WNPTN2F448tb49BoqjV9IMRzZWa0WB8GHzrB+bnxKQPOyzz07T6rmDKrr4Hjl81ADGjDYSsr1fgXcjyLQFdMdw==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-riscv64-gnu": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.136.0.tgz", + "integrity": "sha512-MvcCfu8OeabrmNN9wqhh8Gqizg68RQ3iOvXBJmlLMy5p1PC6az9bAwqOk98lsCmKI4I5LpULekOlfxRYh3o5Kw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-riscv64-musl": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.136.0.tgz", + "integrity": "sha512-rwJXFUFFA0egAULtOOgfW8YhhLhtWrr2qFxftvZ7/U6Xtxiev8i+eCwSNW1RpHiX74QMsJVpRE3ykoefqb1MtQ==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-s390x-gnu": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.136.0.tgz", + "integrity": "sha512-qizAxHmj7VHm5/D5qiIlvzxAS2tlmadDOJ5XFI7zx/wfnk/+fdk5iVaHOv+C4tDE/4wLQyMoBYVIs6R1LeN91w==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-x64-gnu": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.136.0.tgz", + "integrity": "sha512-gcyLAad0UblkEhGjVfx5Mwq63OSx8r04IbcfwzpZYpHisO/vioMsrs4GkDsQZ2Ejc8cdUk6YMIrf9FwNBzh+Dw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-linux-x64-musl": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-linux-x64-musl/-/binding-linux-x64-musl-0.136.0.tgz", + "integrity": "sha512-QAK6kwBjljX6luYzeppVdRP3gTaFNFfp9iS/V0cCvU43wUZay5xFXBgmzBHYrzZV2MhzURNnXfEZRo9poAxyvA==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-openharmony-arm64": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-openharmony-arm64/-/binding-openharmony-arm64-0.136.0.tgz", + "integrity": "sha512-nIGxkTOnN9GwslmooxbZ3Ti2RgbX9QLHy0X9uO0pslOT+4dsNVUawBU/6r4M5cWM+ZdRD2tOF4V+n0l8tS59nQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-wasm32-wasi": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-wasm32-wasi/-/binding-wasm32-wasi-0.136.0.tgz", + "integrity": "sha512-YB3G8AHT6PpDS8/DfjlJrQjywj+/6gCkNBcAJdYzcYbQRIZE6SaTNowiGuZPR1m1jOl1WjcjefniKSmVxzhvRA==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.5" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-win32-arm64-msvc": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.136.0.tgz", + "integrity": "sha512-UVRH3BlNc2rmla+sbSd5xTF/u+esCqZS6VSWPolZybcBh3TlVe1Sf5MOuHQWUaFoyv5uFOvee4hOfS/14nPvZw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-win32-ia32-msvc": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.136.0.tgz", + "integrity": "sha512-zFGkUd82jci0E+isc2O36lVmmXVUDXyEMN2RYIyez+MBNqhdjPLIGx62FCsrJtLKeuiPf/8XkFv977aUtndUng==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-minify/binding-win32-x64-msvc": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/@oxc-minify/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.136.0.tgz", + "integrity": "sha512-VHgPwd5xibC9UYxBeq7nURTjY+wxhdumllij6JaCIUVfD9IRcrA66SVlJ5fPNoUXcRp8ZXhJfoIZwVz9wAEGJw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@polymorph/component-test-js": { + "version": "0.2.0", + "resolved": "git+ssh://git@github.com/polymorph-components/polymorph-test.git#4ce2a7a45d479a22bc8eb2696dd9ebe0b2a3516b", + "integrity": "sha512-3Wnnw4phBfW7L9masextudvg4IFdC1IwBR1FoKj9Nx4d3euL3obbKOEi2lSJW8Nt9mFVARBXCeIwGbOeP7PJdQ==", + "license": "Apache-2.0 WITH LLVM-exception" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/binaryen": { + "version": "130.0.0", + "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-130.0.0.tgz", + "integrity": "sha512-XDrb+zql0RbFPKgj7MuH9zOc78R3Fa/P/VSGnnpdwYsvNZPWjcMYMdAkKCOQEL2A7yqgjSMTDRFp6gfSDW+/QQ==", + "license": "Apache-2.0", + "bin": { + "wasm-as": "bin/wasm-as", + "wasm-ctor-eval": "bin/wasm-ctor-eval", + "wasm-dis": "bin/wasm-dis", + "wasm-merge": "bin/wasm-merge", + "wasm-metadce": "bin/wasm-metadce", + "wasm-opt": "bin/wasm-opt", + "wasm-reduce": "bin/wasm-reduce", + "wasm-shell": "bin/wasm-shell", + "wasm2js": "bin/wasm2js" + } + }, + "node_modules/oxc-minify": { + "version": "0.136.0", + "resolved": "https://registry.npmjs.org/oxc-minify/-/oxc-minify-0.136.0.tgz", + "integrity": "sha512-u9UvPFTYVwQ/i1kpp/c20S/SbvLaLV0Ngzyhgi0YV4B1SlxM7RgnnePl4vY9N3Z9sTRcozL1MRStI3PlUN9RFA==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-minify/binding-android-arm-eabi": "0.136.0", + "@oxc-minify/binding-android-arm64": "0.136.0", + "@oxc-minify/binding-darwin-arm64": "0.136.0", + "@oxc-minify/binding-darwin-x64": "0.136.0", + "@oxc-minify/binding-freebsd-x64": "0.136.0", + "@oxc-minify/binding-linux-arm-gnueabihf": "0.136.0", + "@oxc-minify/binding-linux-arm-musleabihf": "0.136.0", + "@oxc-minify/binding-linux-arm64-gnu": "0.136.0", + "@oxc-minify/binding-linux-arm64-musl": "0.136.0", + "@oxc-minify/binding-linux-ppc64-gnu": "0.136.0", + "@oxc-minify/binding-linux-riscv64-gnu": "0.136.0", + "@oxc-minify/binding-linux-riscv64-musl": "0.136.0", + "@oxc-minify/binding-linux-s390x-gnu": "0.136.0", + "@oxc-minify/binding-linux-x64-gnu": "0.136.0", + "@oxc-minify/binding-linux-x64-musl": "0.136.0", + "@oxc-minify/binding-openharmony-arm64": "0.136.0", + "@oxc-minify/binding-wasm32-wasi": "0.136.0", + "@oxc-minify/binding-win32-arm64-msvc": "0.136.0", + "@oxc-minify/binding-win32-ia32-msvc": "0.136.0", + "@oxc-minify/binding-win32-x64-msvc": "0.136.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + } + } +} diff --git a/bench/boundary/package.json b/bench/boundary/package.json new file mode 100644 index 0000000..10eace6 --- /dev/null +++ b/bench/boundary/package.json @@ -0,0 +1,10 @@ +{ + "name": "boundary-bench", + "private": true, + "type": "module", + "dependencies": { + "@bytecodealliance/jco-transpile": "https://github.com/lann/jco/releases/download/all-fixes-dbad4d7/bytecodealliance-jco-transpile-0.6.3-gdbad4d7.tgz", + "@bytecodealliance/preview2-shim": "https://github.com/lann/jco/releases/download/all-fixes-dbad4d7/bytecodealliance-preview2-shim-0.20.1-gdbad4d7.tgz", + "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#4ce2a7a45d479a22bc8eb2696dd9ebe0b2a3516b" + } +} diff --git a/bench/boundary/sweep.mjs b/bench/boundary/sweep.mjs new file mode 100644 index 0000000..8df919a --- /dev/null +++ b/bench/boundary/sweep.mjs @@ -0,0 +1,50 @@ +// Run the full boundary sweep and print the comparison table. Invoked by +// `just bench-boundary [with-jco]` with the bundle + translator paths; +// lanes: deltic on plain node (callback + jspi) and deno, plus jco under +// node when the npm tree + transpile are present (with-jco). +// +// node sweep.mjs [--with-jco] +import { spawnSync } from "node:child_process"; +import { existsSync } from "node:fs"; + +const [bundle, translator, withJco] = process.argv.slice(2); +const SHAPES = ["send", "recv", "send-sync"]; +const MODES = ["immediate", "microtask"]; +const SIZES = [0, 1200]; +const rows = []; + +function run(cmd, args) { + const r = spawnSync(cmd, args, { encoding: "utf8", cwd: new URL(".", import.meta.url).pathname }); + if (r.status !== 0) throw new Error(`${cmd} ${args.join(" ")}\n${r.stderr}`); + rows.push(JSON.parse(r.stdout.trim().split("\n").at(-1))); +} + +const jco = withJco === "--with-jco" && existsSync(new URL("./generated/bench.js", import.meta.url)); +for (const shape of SHAPES) { + for (const mode of MODES) { + for (const size of SIZES) { + run("node", ["driver-deltic.mjs", bundle, translator, shape, mode, "50000", String(size), "5"]); + run("node", ["--experimental-wasm-jspi", "driver-deltic.mjs", bundle, translator, shape, mode, "20000", String(size), "5", "jspi"]); + run("deno", ["run", "-A", "driver-deltic.mjs", bundle, translator, shape, mode, "50000", String(size), "5"]); + if (jco) { + const iters = shape === "send-sync" ? "50000" : "1500"; + run("node", ["--experimental-wasm-jspi", "driver-jco.mjs", shape, mode, iters, String(size), "5"]); + } + } + } +} + +const lanes = [...new Set(rows.map((r) => r.lane))]; +const pad = (s, n) => String(s).padEnd(n); +console.log(pad("shape", 10) + pad("mode", 11) + pad("size", 6) + lanes.map((l) => String(l).padStart(22)).join("")); +for (const shape of SHAPES) { + for (const mode of MODES) { + for (const size of SIZES) { + const cells = lanes.map((lane) => { + const r = rows.find((x) => x.lane === lane && x.shape === shape && x.mode === mode && x.size === size); + return (r ? `${r.callsPerSec.toLocaleString("en-US")}/s` : "-").padStart(22); + }); + console.log(pad(shape, 10) + pad(mode, 11) + pad(size, 6) + cells.join("")); + } + } +} diff --git a/justfile b/justfile index a85b152..eb5ef59 100644 --- a/justfile +++ b/justfile @@ -162,6 +162,30 @@ smoke-c0: shim websocket-conformance: shim cd ports/websocket && deno task conformance +# The host-boundary microbench (bench/boundary/README.md): calls/sec per +# ABI shape for the CURRENT tree, on plain node (callback + jspi) and +# deno. Manual instrument, not a gate — numbers are box-relative; the +# committed README carries the baseline and the issues it feeds (#8, +# #54; #17's record). `just bench-boundary with-jco` adds the incumbent +# jco lane (npm tree + transpile, prepared on first use). +bench-boundary *jco: shim + #!/usr/bin/env bash + set -euo pipefail + (cd bench/boundary/guest && cargo build --release --target wasm32-wasip2) + deno run -A tools/release-bundle/build.ts --out bench/boundary/deltic-embedder.local.mjs + if [ "{{jco}}" = "with-jco" ]; then + cd bench/boundary + [ -d node_modules ] || npm ci --no-audit --no-fund + node jco-transpile.mjs transpile guest/target/wasm32-wasip2/release/boundary_bench_guest.wasm \ + --name bench -I async -o generated + cd ../.. + node bench/boundary/sweep.mjs deltic-embedder.local.mjs \ + ../../target/wasm32-unknown-unknown/release/translator_shim.wasm --with-jco + else + node bench/boundary/sweep.mjs deltic-embedder.local.mjs \ + ../../target/wasm32-unknown-unknown/release/translator_shim.wasm + fi + # The iroh endpoint exit exam (needs iroh-relay on PATH). iroh-exam: shim deno run -A --unstable-net exams/iroh-endpoint/run.ts