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.
@@ -618,10 +618,10 @@ Each runtime runs the real `bench()` lib (warm up, time the loop, report MB/s it
-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