diff --git a/README.md b/README.md index ed9bdd4a..8d9bae5b 100644 --- a/README.md +++ b/README.md @@ -606,9 +606,9 @@ How `json-as` stacks up against other JSON libraries on a ~5 KiB GitHub-repo pay ### Runtime Comparison -How fast the **same** `json-as` classic bench deserializes the minified payloads across six WebAssembly runtimes — including [WARP](https://github.com/wasm-ecosystem/wasm-compiler), a single-pass compiler built for embedded targets, alongside the optimizing JITs (Wasmtime, WAVM), the pure-Go wazero, and JS engines (V8, Bun). +How fast the **same** `json-as` classic bench deserializes the minified payloads across six WebAssembly runtimes — including [WAGO](https://github.com/wago-org/wago), a pure-Go, no-cgo single-pass JIT, alongside the optimizing JITs (Wasmtime, WAVM), the pure-Go wazero, and JS engines (V8, Bun). -Each runtime runs the real `bench()` lib (warm up, time the loop, report MB/s itself) on a `NAIVE`-mode build with a shared feature set (no SIMD / bulk-memory / non-trapping float-to-int) so the executed code is equivalent. The WASI runtimes read the payload over WASI; V8/Bun via an `env`-ABI host. WARP has no WASI and — by design ("no recursions") — can't re-enter the module from a host import, so it runs through a small custom C++ host that links `performance.now`/`console.log`/`writeFile` with the payload embedded. The timed run is split into small frames with a full GC between them (the bench lib's `BENCH_FRAMES`, applied to every runtime so the measurement is identical); this excludes stop-the-world GC pauses and keeps WARP — an embedded, single-shot-oriented compiler — inside its stable envelope. Even so, WARP's single-pass codegen lands within a few percent of the optimizing JITs. +Each runtime runs the real `bench()` lib (warm up, time the loop, report MB/s itself) on a `NAIVE`-mode build with a shared feature set (no SIMD / bulk-memory / non-trapping float-to-int) so the executed code is equivalent. The WASI runtimes read the payload over WASI; V8/Bun use an `env`-ABI host. WAGO runs through a small Go host built against its public API with the documented `wago_guardpage` mode; the payload is embedded so the comparison does not require a filesystem plugin, while `performance.now`/`console.log`/`writeFile` still keep measurement and result reporting inside the real guest benchmark. Deserialization throughput across WebAssembly runtimes @@ -618,10 +618,10 @@ Each runtime runs the real `bench()` lib (warm up, time the loop, report MB/s it Serialization throughput across WebAssembly runtimes -Reproduce locally (the JS engines and standalone runtimes are auto-detected; point `WARP_SRC` at a [wasm-ecosystem/wasm-compiler](https://github.com/wasm-ecosystem/wasm-compiler) checkout with its libs built — see the script header for the cmake flags): +Reproduce locally (the JS engines and standalone runtimes are auto-detected; point `WAGO_SRC` at a [wago-org/wago](https://github.com/wago-org/wago) checkout so the small benchmark host can be built): ```bash -WARP_SRC=/path/to/wasm-compiler npm run bench:runtimes +WAGO_SRC=/path/to/wago npm run bench:runtimes npm run charts:runtimes ``` diff --git a/assembly/__benches__/lib/bench.ts b/assembly/__benches__/lib/bench.ts index 34211987..a26cbc30 100644 --- a/assembly/__benches__/lib/bench.ts +++ b/assembly/__benches__/lib/bench.ts @@ -187,10 +187,10 @@ export function bench( // Optional framed measurement: split the timed run into BENCH_FRAMES frames // and run a full __collect() (untimed) between them. Each frame's allocation // churn stays bounded and the heap is reset between frames, which keeps - // runtimes that destabilize under one long single-shot allocation loop (e.g. - // WARP) inside their safe envelope while still timing the same total ops. The - // between-frame GC pauses are excluded from `elapsed`; the incremental GC that - // runs *within* each frame is still timed, exactly as in the unframed loop. + // memory-constrained or single-shot runtimes inside their safe envelope while + // still timing the same total ops. The between-frame GC pauses are excluded + // from `elapsed`; the incremental GC that runs *within* each frame is still + // timed, exactly as in the unframed loop. // Defaults to a single frame (identical to the original behavior). // @ts-expect-error: BENCH_FRAMES may be undefined. const frames: u64 = isDefined(BENCH_FRAMES) ? u64(BENCH_FRAMES) : 1; diff --git a/bench/runners/wago_host.go b/bench/runners/wago_host.go new file mode 100644 index 00000000..e5291386 --- /dev/null +++ b/bench/runners/wago_host.go @@ -0,0 +1,116 @@ +// Command wago_host runs a generated json-as runtime benchmark on WAGO. +// +// The benchmark keeps timing inside WebAssembly. This host only supplies the +// small env ABI used by the AssemblyScript bench library and forwards result +// records to stdout for scripts/run-bench.runtimes.sh. +package main + +import ( + "encoding/binary" + "fmt" + "os" + "time" + "unicode/utf16" + + wago "github.com/wago-org/wago" +) + +var epoch = time.Now() + +func liftString(m wago.HostModule, ptr uint32) string { + mem := m.Memory() + if ptr == 0 || ptr < 4 || uint64(ptr) > uint64(len(mem)) { + return "" + } + + byteLen := binary.LittleEndian.Uint32(mem[ptr-4 : ptr]) + end := uint64(ptr) + uint64(byteLen) + if byteLen%2 != 0 || end > uint64(len(mem)) { + return "" + } + + units := make([]uint16, byteLen/2) + for i := range units { + off := int(ptr) + i*2 + units[i] = binary.LittleEndian.Uint16(mem[off : off+2]) + } + return string(utf16.Decode(units)) +} + +func fail(format string, args ...any) { + fmt.Fprintf(os.Stderr, "wago_host: "+format+"\n", args...) + os.Exit(1) +} + +func trace(message string) { + if os.Getenv("WAGO_HOST_TRACE") != "" { + fmt.Fprintln(os.Stderr, "wago_host:", message) + } +} + +func main() { + if len(os.Args) != 2 { + fail("usage: wago_host ") + } + + wasm, err := os.ReadFile(os.Args[1]) + if err != nil { + fail("read module: %v", err) + } + if !wago.GuardPageSupported() { + fail("guard-page bounds are unavailable; build with -tags wago_guardpage") + } + + trace("compiling module") + config := wago.NewRuntimeConfig().WithBoundsChecks(wago.BoundsChecksSignalsBased) + compiled, err := wago.Compile(config, wasm) + if err != nil { + fail("compile module: %v", err) + } + defer compiled.Close() + trace("module compiled") + + imports := wago.Imports{ + "env.performance.now": wago.HostFunc(func(_ wago.HostModule, _ []uint64, results []uint64) { + results[0] = wago.F64(float64(time.Since(epoch).Nanoseconds()) / 1e6) + }), + "env.Date.now": wago.HostFunc(func(_ wago.HostModule, _ []uint64, results []uint64) { + results[0] = wago.F64(float64(time.Now().UnixNano()) / 1e6) + }), + "env.console.log": wago.HostFunc(func(m wago.HostModule, params, _ []uint64) { + fmt.Println(liftString(m, uint32(params[0]))) + }), + "env.writeFile": wago.HostFunc(func(m wago.HostModule, params, _ []uint64) { + name := liftString(m, uint32(params[0])) + data := liftString(m, uint32(params[1])) + fmt.Printf("__AS_BENCH_JSON__%s\t%s\n", name, data) + }), + "env.abort": wago.HostFunc(func(m wago.HostModule, params, _ []uint64) { + fmt.Fprintf( + os.Stderr, + "abort: %s in %s:%d:%d\n", + liftString(m, uint32(params[0])), + liftString(m, uint32(params[1])), + uint32(params[2]), + uint32(params[3]), + ) + panic(wago.HostExit{Code: 1}) + }), + } + + trace("instantiating module") + instance, err := wago.Instantiate(compiled, wago.InstantiateOptions{Imports: imports}) + if err != nil { + fail("instantiate module: %v", err) + } + defer instance.Close() + trace("module instantiated") + + // The module is built with --exportStart so initialization and the full + // benchmark run through WAGO's normal exported-function invocation path. + trace("running benchmark") + if _, err := instance.Invoke("start"); err != nil { + fail("run benchmark: %v", err) + } + trace("benchmark finished") +} diff --git a/bench/runners/warp_host.cpp b/bench/runners/warp_host.cpp deleted file mode 100644 index 21348bf8..00000000 --- a/bench/runners/warp_host.cpp +++ /dev/null @@ -1,143 +0,0 @@ -// Custom WARP (wasm-ecosystem/wasm-compiler) host for the cross-runtime -// benchmark. Unlike WARP's stock `vb_bench` (which links no imports and times an -// export externally), this host links the `env` functions the json-as bench lib -// needs - performance.now / Date.now / console.log / writeFile / abort - so WARP -// runs the *real* bench() loop and self-measures exactly like every other -// runtime, emitting the same `__AS_BENCH_JSON__` result lines. -// -// WARP has no WASI and - by design ("no recursions", static execution context) - -// cannot re-enter the module from inside a host import, so readFile (which would -// have to call the wasm __new allocator) is impossible. The WARP bench build -// therefore embeds its payload instead of reading a file; the measured -// deserialize/serialize work is identical. WARP also destabilizes under one long -// single-shot allocation loop, so run-bench.runtimes.sh builds with BENCH_FRAMES -// (the bench lib splits the timed run into small GC-separated frames). -// -// Build (links the static libs from a WARP build tree, see run-bench.runtimes.sh): -// g++ -std=gnu++14 -O2 -DJIT_TARGET_X86_64 -DINTERRUPTION_REQUEST=0 \ -// -DEAGER_ALLOCATION=1 -I"$WARP_SRC" warp_host.cpp \ -// -Wl,--start-group .a \ -// -Wl,--end-group -lpthread -o warp_host -// -// Usage: warp_host -// Prints whatever the bench writes (progress + __AS_BENCH_JSON__\t). -#include -#include -#include -#include -#include -#include -#include - -#include "src/WasmModule/WasmModule.hpp" -#include "src/core/common/NativeSymbol.hpp" -#include "src/core/common/function_traits.hpp" -#include "src/utils/STDCompilerLogger.hpp" - -using namespace vb; -using Clock = std::chrono::high_resolution_clock; - -namespace { -WasmModule *g_module = nullptr; -Clock::time_point g_epoch; - -// Reads an AssemblyScript string (UTF-16LE, byte-length stored as the u32 at -// ptr-4) out of linear memory and returns it as UTF-8. -std::string liftString(uint32_t ptr) { - if (ptr == 0U || g_module == nullptr) return std::string(); - uint8_t const *lenField = g_module->getLinearMemoryRegion(ptr - 4U, 4U); - uint32_t byteLen = 0U; - std::memcpy(&byteLen, lenField, 4U); - if (byteLen == 0U) return std::string(); - uint8_t const *data = g_module->getLinearMemoryRegion(ptr, byteLen); - std::string out; - out.reserve(byteLen / 2U); - for (uint32_t i = 0U; i + 1U < byteLen; i += 2U) { - uint32_t cu = static_cast(data[i]) | (static_cast(data[i + 1U]) << 8); - // Minimal UTF-16 -> UTF-8 (the bench's strings are ASCII JSON + log text; - // surrogate pairs are passed through per-unit, which is fine for output). - if (cu < 0x80U) { - out.push_back(static_cast(cu)); - } else if (cu < 0x800U) { - out.push_back(static_cast(0xC0U | (cu >> 6))); - out.push_back(static_cast(0x80U | (cu & 0x3FU))); - } else { - out.push_back(static_cast(0xE0U | (cu >> 12))); - out.push_back(static_cast(0x80U | ((cu >> 6) & 0x3FU))); - out.push_back(static_cast(0x80U | (cu & 0x3FU))); - } - } - return out; -} - -// --- env imports the bench lib calls (none re-enter the module) ------------- -double host_performance_now(void *) noexcept { - return std::chrono::duration(Clock::now() - g_epoch).count(); -} -double host_date_now(void *) noexcept { - return std::chrono::duration(Clock::now().time_since_epoch()).count(); -} -void host_console_log(uint32_t ptr, void *) noexcept { printf("%s\n", liftString(ptr).c_str()); } - -// dumpToFile() calls writeFile(path, json). The env build's path is -// ./build/logs/as//..as.json; re-emit it as an -// __AS_BENCH_JSON__ line so run-bench.runtimes.sh routes it to runtimes/warp/. -void host_write_file(uint32_t namePtr, uint32_t dataPtr, void *) noexcept { - printf("__AS_BENCH_JSON__%s\t%s\n", liftString(namePtr).c_str(), liftString(dataPtr).c_str()); -} -void host_abort(uint32_t msg, uint32_t file, uint32_t line, uint32_t col, void *) noexcept { - printf("abort: %s in %s:%u:%u\n", liftString(msg).c_str(), liftString(file).c_str(), line, col); - std::exit(1); -} - -std::vector loadFile(char const *path) { - FILE *f = fopen(path, "rb"); - if (f == nullptr) { - fprintf(stderr, "warp_host: cannot open %s\n", path); - std::exit(1); - } - fseek(f, 0, SEEK_END); - long n = ftell(f); - rewind(f); - std::vector buf(static_cast(n)); - size_t rd = fread(buf.data(), 1U, buf.size(), f); - (void)rd; - fclose(f); - return buf; -} -} // namespace - -int main(int argc, char **argv) { - if (argc < 2) { - fprintf(stderr, "usage: warp_host \n"); - return 1; - } - std::vector bytecode = loadFile(argv[1]); - g_epoch = Clock::now(); - - WasmModule::initEnvironment(&malloc, &realloc, &free); - STDCompilerLogger logger{}; - WasmModule module(UINT64_MAX, logger, false, nullptr, 0U); - g_module = &module; - - // V1 imports (statically linked at compile time -> pass to compile(), and an - // EMPTY span to initFromCompiledBinary, which rejects STATIC symbols). - auto imports = make_array( - STATIC_LINK("env", "performance.now", host_performance_now), - STATIC_LINK("env", "Date.now", host_date_now), - STATIC_LINK("env", "console.log", host_console_log), - STATIC_LINK("env", "writeFile", host_write_file), - STATIC_LINK("env", "abort", host_abort)); - Span importSpan(imports.data(), imports.size()); - - try { - WasmModule::CompileResult compiled{module.compile( - Span(bytecode.data(), static_cast(bytecode.size())), importSpan)}; - module.initFromCompiledBinary(compiled.getModule().span(), Span(), Span()); - module.start(nullptr); // runs the bench (all work happens in the start section) - } catch (std::exception const &e) { - fprintf(stderr, "warp_host: %s\n", e.what()); - return 1; - } - return 0; -} diff --git a/scripts/build-chart-runtimes.ts b/scripts/build-chart-runtimes.ts index 7928751a..9f53cdbd 100644 --- a/scripts/build-chart-runtimes.ts +++ b/scripts/build-chart-runtimes.ts @@ -1,10 +1,10 @@ // Cross-runtime throughput chart for the classic payloads. Compares how fast the // SAME json-as NAIVE-mode bench deserializes the minified payloads under six -// WebAssembly runtimes (WARP / wasmtime / wavm / wazero / v8 / bun). Every bar is +// WebAssembly runtimes (WAGO / wasmtime / wavm / wazero / v8 / bun). Every bar is // the runtime's own bench()-reported MB/s - see scripts/run-bench.runtimes.sh. // // Populate the logs first: -// WARP_SRC=/path/to/wasm-compiler bash scripts/run-bench.runtimes.sh +// WAGO_SRC=/path/to/wago bash scripts/run-bench.runtimes.sh // Then: // bun scripts/build-chart-runtimes.ts import fs from "node:fs"; @@ -16,11 +16,11 @@ import { } from "./lib/bench-utils"; import { rgba, BASE } from "./lib/palette"; -// One distinct hue per runtime; WARP (the subject) gets the hero blue. +// One distinct hue per runtime; WAGO (the subject) gets the hero blue. const RUNTIMES: { key: string; label: string; bg: string; border: string }[] = [ { - key: "warp", - label: "WARP", + key: "wago", + label: "WAGO", bg: rgba("pacificBlue", 0.9), border: BASE.pacificBlue, }, diff --git a/scripts/build-charts.sh b/scripts/build-charts.sh index b3965b34..91d0de6a 100755 --- a/scripts/build-charts.sh +++ b/scripts/build-charts.sh @@ -52,9 +52,9 @@ JSON_CHART_RUNTIME="$CHART_RUNTIME" bun ./scripts/build-library-deserialize.ts bun ./scripts/build-lazy.ts # Classic-dataset mode comparison (NAIVE/SWAR/SIMD + lazy, no JS baseline) bun ./scripts/build-chart-classic.ts -# Cross-runtime comparison (WARP/wasmtime/wasmer/wavm/v8/bun). Opt-in: only built +# Cross-runtime comparison (WAGO/wasmtime/wavm/wazero/v8/bun). Opt-in: only built # when scripts/run-bench.runtimes.sh has produced logs (it needs external -# runtimes + a WARP vb_bench build), so the default chart build never fails on it. +# runtimes), so the default chart build never fails on it. if compgen -G "./build/logs/runtimes/*/*.deserialize.json" >/dev/null 2>&1; then bun ./scripts/build-chart-runtimes.ts fi diff --git a/scripts/gen-wago-bench.mjs b/scripts/gen-wago-bench.mjs new file mode 100644 index 00000000..388a5703 --- /dev/null +++ b/scripts/gen-wago-bench.mjs @@ -0,0 +1,42 @@ +// Generates the WAGO variant of a classic bench. The WAGO host intentionally +// exposes only timing, logging, result writing, and abort imports, so payloads +// are embedded instead of requiring a filesystem plugin. The generated module +// still uses the real bench()/dumpToFile calls and measures the same JSON work. +// +// Usage: node scripts/gen-wago-bench.mjs +import fs from "node:fs"; +import path from "node:path"; + +const [, , name, outPath] = process.argv; +if (!outPath) { + console.error("usage: gen-wago-bench.mjs "); + process.exit(1); +} + +let src = fs.readFileSync( + path.resolve(`assembly/__benches__/classic/${name}.bench.ts`), + "utf8", +); + +// Drop test-only assertions, which are not part of the measured workload. +src = src + .split("\n") + .filter((line) => !/from\s+["'].*__tests__\/lib["']/.test(line)) + .filter((line) => !/^\s*expect\(/.test(line)) + .join("\n"); + +// Replace every readFile("") call with the file's escaped contents. +src = src.replace(/readFile\(\s*"([^"]+)"\s*,?\s*\)/g, (_match, filePath) => + JSON.stringify(fs.readFileSync(path.resolve(filePath), "utf8")), +); + +// The other bindings from the bench helper import remain in use. +src = src.replace(/^\s*readFile,\s*$/m, ""); + +const header = `// AUTO-GENERATED by scripts/gen-wago-bench.mjs - do not edit. +// WAGO build of the "${name}" classic bench: identical schema and bench calls, +// with its payload embedded. Results are emitted through the WAGO host. +`; + +fs.writeFileSync(path.resolve(outPath), header + src); +console.log(`> ${outPath}`); diff --git a/scripts/gen-warp-bench.mjs b/scripts/gen-warp-bench.mjs deleted file mode 100644 index 373e17d7..00000000 --- a/scripts/gen-warp-bench.mjs +++ /dev/null @@ -1,55 +0,0 @@ -// Generates the WARP variant of a classic bench. WARP has no WASI and can't -// re-enter the module from a host import, so it can't read the payload through -// the bench lib's readFile (which allocates via __new). This rewrites the real -// assembly/__benches__/classic/.bench.ts into an equivalent that embeds -// the payload instead - every readFile("...path...") call is replaced by the -// file's contents as a string literal - while keeping the identical schema and -// bench()/dumpToFile calls, so WARP self-measures the same workload via the real -// bench lib (timed by warp_host's performance.now). -// -// Usage: node scripts/gen-warp-bench.mjs -import fs from "node:fs"; -import path from "node:path"; - -const [, , name, outPath] = process.argv; -if (!outPath) { - console.error("usage: gen-warp-bench.mjs "); - process.exit(1); -} - -let src = fs.readFileSync( - path.resolve(`assembly/__benches__/classic/${name}.bench.ts`), - "utf8", -); - -// 1. Drop the test-only `expect` import and any expect(...) assertion statements -// (they pull in test infra and aren't part of the measured workload). -src = src - .split("\n") - .filter((l) => !/from\s+["'].*__tests__\/lib["']/.test(l)) - .filter((l) => !/^\s*expect\(/.test(l)) - .join("\n"); - -// 2. Replace every readFile("") (single- or multi-line) with the embedded -// file contents as a fully-escaped string literal. -src = src.replace(/readFile\(\s*"([^"]+)"\s*,?\s*\)/g, (_m, p) => - JSON.stringify(fs.readFileSync(path.resolve(p), "utf8")), -); - -// 3. Drop the now-unused readFile import binding so asc doesn't flag it. The -// import list also brings bench/dumpToFile/blackbox/utf8ByteLength, which stay. -src = src.replace(/^\s*readFile,\s*$/m, ""); - -// The full iteration counts are kept: WARP destabilizes only under one long -// single-shot allocation loop, which run-bench.runtimes.sh avoids by building -// with BENCH_FRAMES (the bench lib splits the run into small GC-separated -// frames). No per-bench cap is needed. - -const header = `// AUTO-GENERATED by scripts/gen-warp-bench.mjs - do not edit. -// WARP build of the "${name}" classic bench: identical schema + bench() calls, -// but payloads are embedded (WARP has no WASI / host re-entrancy). Measured by -// the real bench lib; results emitted via warp_host's writeFile shim. -`; - -fs.writeFileSync(path.resolve(outPath), header + src); -console.log(`> ${outPath}`); diff --git a/scripts/run-bench.runtimes.sh b/scripts/run-bench.runtimes.sh index 353da004..3e6d6118 100755 --- a/scripts/run-bench.runtimes.sh +++ b/scripts/run-bench.runtimes.sh @@ -4,7 +4,7 @@ # inside v8/wavm), this compares how fast the SAME json-as workload runs across # different WebAssembly runtimes: # -# WARP (wasm-ecosystem/wasm-compiler) · wasmtime · wavm · wazero · v8 · bun +# WAGO · wasmtime · wavm · wazero · v8 · bun # # Every runtime runs the REAL classic bench (assembly/__benches__/classic/. # bench.ts) through the actual bench() lib - it reads the payload, warms up, @@ -17,18 +17,13 @@ # stdout as __AS_BENCH_JSON__ lines) # * env build -> v8 / bun (env-ABI host bench/runners/ # runtimes-env.mjs supplies readFile/performance.now/...) -# * WARP build -> warp_host (WARP has no WASI and can't -# re-enter the module from a host import, so its payload is -# embedded and its iteration count capped; see -# scripts/gen-warp-bench.mjs. Still measured by the real -# bench() via warp_host's performance.now.) +# * WAGO build -> wago_host (payload embedded; the small Go +# host supplies timing/logging/result imports; see +# scripts/gen-wago-bench.mjs) # -# WARP is a C++ library with no standalone runner, so it's compiled from source. -# Point WARP_SRC at a wasm-ecosystem/wasm-compiler checkout that has a build dir -# with the static libs (see docs/setup/Build.md). Configure that build with: -# cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DVB_ENABLE_DEV_FEATURE=OFF \ -# -DENABLE_BENCH=1 -DCMAKE_CXX_FLAGS="-DINTERRUPTION_REQUEST=0 -DEAGER_ALLOCATION=1" .. -# ninja vb_libWasmModule +# The WAGO host uses WAGO's public Go API and its guard-page bounds mode. Point +# WAGO_SRC at a wago-org/wago checkout; the script builds the host against that +# checkout automatically. set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" @@ -41,13 +36,9 @@ LOG_DIR="build/logs/runtimes" ENV_RUNNER="bench/runners/runtimes-env.mjs" mkdir -p "$GEN_DIR" "$WASM_DIR" "$LOG_DIR" -# Common asc flags: NAIVE, WARP-compatible feature subset, so all runtimes -# execute equivalent code. BENCH_FRAMES splits each timed run into that many -# small frames with a full (untimed) GC between them - applied to EVERY runtime -# so the measurement is identical, and required for WARP, which destabilizes -# under one long single-shot allocation loop. With the classic benches' counts -# (<=4000 ops) this keeps every frame at <=100 iterations, well inside WARP's -# stable envelope. +# Common asc flags: NAIVE with a portable feature subset, so all runtimes +# execute equivalent code. Split allocation-heavy runs into equal, GC-separated +# frames to keep long classic loops within a stable memory envelope everywhere. BENCH_FRAMES="${BENCH_FRAMES:-40}" COMMON_FLAGS=(-O3 --noAssert --uncheckedBehavior always --runtime incremental --disable nontrapping-f2i --disable bulk-memory --use "BENCH_FRAMES=$BENCH_FRAMES") @@ -56,30 +47,22 @@ PAYLOADS=("twitter" "citm_catalog" "canada") has() { command -v "$1" >/dev/null 2>&1; } -# --- locate / build the WARP host ----------------------------------------- -WARP_SRC="${WARP_SRC:-}" -WARP_HOST="${WARP_HOST:-}" -if [[ -z "$WARP_SRC" ]]; then - for cand in "$ROOT_DIR/build/warp/wasm-compiler" "$HOME/wasm-compiler" "/tmp/warp-wc"; do - [[ -d "$cand" ]] && WARP_SRC="$cand" && break +# --- locate / build the WAGO host ----------------------------------------- +WAGO_SRC="${WAGO_SRC:-}" +WAGO_HOST="${WAGO_HOST:-}" +if [[ -z "$WAGO_SRC" ]]; then + for cand in "$HOME/Code/Wago/wago" "$HOME/wago" "/tmp/wago"; do + [[ -f "$cand/go.mod" ]] && WAGO_SRC="$cand" && break done fi -build_warp_host() { - [[ -n "$WARP_HOST" && -x "$WARP_HOST" ]] && return 0 - [[ -z "$WARP_SRC" || ! -d "$WARP_SRC" ]] && return 1 - local build_dir libs - local lib_dir - lib_dir="$(find "$WARP_SRC" -name 'libvb_libWasmModule.a' -printf '%h\n' 2>/dev/null | head -1)" - [[ -z "$lib_dir" ]] && { echo " (WARP libs not found under $WARP_SRC - build them, see header)"; return 1; } - # lib_dir is /src/WasmModule; the build root is two levels up. - build_dir="$(cd "$lib_dir/../.." && pwd)" - WARP_HOST="$WASM_DIR/warp_host" - echo "==> building warp_host (WARP_SRC=$WARP_SRC)" - g++ -std=gnu++14 -O2 -DJIT_TARGET_X86_64 -DINTERRUPTION_REQUEST=0 -DEAGER_ALLOCATION=1 -I"$WARP_SRC" \ - bench/runners/warp_host.cpp -Wl,--start-group \ - "$build_dir"/src/WasmModule/libvb_libWasmModule.a "$build_dir"/src/core/compiler/libvb_libcompiler.a \ - "$build_dir"/src/core/runtime/libvb_libruntime.a "$build_dir"/src/utils/libvb_libutils.a \ - "$build_dir"/src/core/common/libvb_lib_core_common.a -Wl,--end-group -lpthread -o "$WARP_HOST" +build_wago_host() { + [[ -n "$WAGO_HOST" && -x "$WAGO_HOST" ]] && return 0 + [[ -z "$WAGO_SRC" || ! -f "$WAGO_SRC/go.mod" ]] && return 1 + has go || { echo " (Go not found; cannot build the WAGO host)"; return 1; } + WAGO_HOST="$WASM_DIR/wago_host" + echo "==> building wago_host (WAGO_SRC=$WAGO_SRC)" + (cd "$WAGO_SRC" && go build -mod=readonly -tags wago_guardpage \ + -o "$WAGO_HOST" "$ROOT_DIR/bench/runners/wago_host.go") } # Captures a runner's stdout: each __AS_BENCH_JSON__\t line is written @@ -103,7 +86,7 @@ capture() { echo " $runtime: $count result(s)" } -build_warp_host || echo " (WARP skipped - set WARP_SRC to a wasm-compiler checkout with built libs)" +build_wago_host || echo " (WAGO skipped - set WAGO_SRC to a wago-org/wago checkout)" for name in "${PAYLOADS[@]}"; do classic="assembly/__benches__/classic/$name.bench.ts" @@ -128,13 +111,14 @@ for name in "${PAYLOADS[@]}"; do has bun && bun "$ENV_RUNNER" "$env_wasm" 2>/dev/null | capture bun fi - # --- WARP build: warp_host (embedded payload, capped iterations) -------- - if [[ -n "$WARP_HOST" && -x "$WARP_HOST" ]]; then - warp_src="$GEN_DIR/$name.warp.ts" - warp_wasm="$WASM_DIR/$name.warp.wasm" - node scripts/gen-warp-bench.mjs "$name" "$warp_src" >/dev/null - JSON_MODE=NAIVE npx asc "$warp_src" --transform ./transform -o "$warp_wasm" "${COMMON_FLAGS[@]}" - "$WARP_HOST" "$warp_wasm" 2>/dev/null | capture warp + # --- WAGO build: embedded payload + small Go host ----------------------- + if [[ -n "$WAGO_HOST" && -x "$WAGO_HOST" ]]; then + wago_src="$GEN_DIR/$name.wago.ts" + wago_wasm="$WASM_DIR/$name.wago.wasm" + node scripts/gen-wago-bench.mjs "$name" "$wago_src" >/dev/null + JSON_MODE=NAIVE npx asc "$wago_src" --transform ./transform -o "$wago_wasm" \ + "${COMMON_FLAGS[@]}" --exportStart start + "$WAGO_HOST" "$wago_wasm" | capture wago fi done