From 9461d0e0041baf87085c9107129b9690fec8880d Mon Sep 17 00:00:00 2001 From: Frederic BIDON Date: Mon, 17 Aug 2026 13:22:01 +0200 Subject: [PATCH] chore: retire browser+wasi prototype Signed-off-by: Frederic BIDON --- hack/browser/.gitignore | 2 - hack/browser/README.md | 54 ------------------- hack/browser/fixture.js | 21 -------- hack/browser/index.html | 15 ------ hack/browser/node-probe.js | 99 ---------------------------------- hack/browser/package-lock.json | 20 ------- hack/browser/package.json | 13 ----- hack/browser/probe.js | 44 --------------- hack/browser/worker.js | 84 ----------------------------- 9 files changed, 352 deletions(-) delete mode 100644 hack/browser/.gitignore delete mode 100644 hack/browser/README.md delete mode 100644 hack/browser/fixture.js delete mode 100644 hack/browser/index.html delete mode 100644 hack/browser/node-probe.js delete mode 100644 hack/browser/package-lock.json delete mode 100644 hack/browser/package.json delete mode 100644 hack/browser/probe.js delete mode 100644 hack/browser/worker.js diff --git a/hack/browser/.gitignore b/hack/browser/.gitignore deleted file mode 100644 index 05f49c9b..00000000 --- a/hack/browser/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -genspec-wasi.wasm diff --git a/hack/browser/README.md b/hack/browser/README.md deleted file mode 100644 index bc37f3f3..00000000 --- a/hack/browser/README.md +++ /dev/null @@ -1,54 +0,0 @@ - - -# Browser probe - -A throwaway check that the `wasip1` artifact runs in a browser, and what it costs. Not the -playground — the seed of one. - -```sh -npm install -GOOS=wasip1 GOARCH=wasm go build -o genspec-wasi.wasm ../../cmd/genspec-wasi - -node node-probe.js # the whole thing without a browser -npm run serve # then open http://localhost:8099/ for the real one -``` - -`genspec-wasi.wasm` and `node_modules/` are generated; neither is committed. - -## What it establishes - -Under [`@bjorn3/browser_wasi_shim`](https://github.com/bjorn3/browser_wasi_shim) (114 KB, no -dependencies), scanning a module held entirely in the shim's in-memory filesystem: - -| workload | run | `poll_oneoff` | definitions | -|---|---|---|---| -| 1 package | 86 ms cold, 10 ms warm | 0 calls | 1 | -| 50 packages, 150 files | 492 ms | 0 calls | 750 | -| 200 packages, 600 files | 3548 ms | 0 calls | 6000 | - -Compiling the 14.2 MB module takes 22 ms once; instantiating is ~4–5 ms and does not grow with the -workload. - -**`poll_oneoff` never fires.** It was the risk worth checking, because the shim implements it by -spinning (`while (endTime > getNow()) {}`) rather than yielding. Go reaches it only through the -netpoller, and only when the scheduler runs out of work — which a single-goroutine scan never does. -It is not guaranteed to stay at zero: longer runs under wazero showed 9–14 calls. But the shim -supports exactly the shape Go emits (one clock subscription, which is all `netpollinit` ever -registers), so when it does fire it works, and the cost is bounded by the delay Go asked for. -That is a reason to keep the scan in a worker, not a reason to avoid this shim. - -Measured under Node, which shares V8 with Chrome. What it does not measure is a browser engine's -own compile time for a 14 MB module, or anything about Firefox and Safari. - -## One run per instance - -The module exports `_start` and `memory`, and nothing else. It is a WASI **command**: run to -completion, terminated by `proc_exit`. Go's `wasip1` target emits no reactor form — there is no -`_initialize` and no callable export — so a fresh instance per scan is forced rather than chosen. - -That costs nothing worth avoiding. Compile once, keep the `WebAssembly.Module`, and instantiate per -run; the measurements above are with the module reused across runs. It also means each scan starts -on a clean heap, so nothing leaks from one run into the next. diff --git a/hack/browser/fixture.js b/hack/browser/fixture.js deleted file mode 100644 index ecbdc7f6..00000000 --- a/hack/browser/fixture.js +++ /dev/null @@ -1,21 +0,0 @@ -// The module the probe scans. Kept in one place so the worker and any later app agree on it. -export const goMod = `module example.com/demo - -go 1.25.0 -`; - -export const petSrc = `package models - -// Pet is an animal in the store. -// -// swagger:model pet -type Pet struct { - // The pet's identifier - // required: true - ID int64 \`json:"id"\` - - // The pet's name - // max length: 50 - Name string \`json:"name"\` -} -`; diff --git a/hack/browser/index.html b/hack/browser/index.html deleted file mode 100644 index e64e70e2..00000000 --- a/hack/browser/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - -codescan WASI probe - -

codescan WASI probe

-

Runs the wasip1 artifact under @bjorn3/browser_wasi_shim, in a worker, and reports what it cost.

-
running…
-

timings


-

stdout (the specification)


-

stderr


-
diff --git a/hack/browser/node-probe.js b/hack/browser/node-probe.js
deleted file mode 100644
index 55725abb..00000000
--- a/hack/browser/node-probe.js
+++ /dev/null
@@ -1,99 +0,0 @@
-// The same run as the page, under Node, so the shim can be exercised without a browser.
-//
-// What this does NOT tell us: how long a browser engine takes to compile a 15 MB module. Everything
-// else - the shim's filesystem, its poll_oneoff, and whether the artifact produces the right spec -
-// is the same code.
-
-import { readFile } from "node:fs/promises";
-import {
-  WASI, File, Directory, OpenFile, ConsoleStdout, PreopenDirectory,
-} from "./node_modules/@bjorn3/browser_wasi_shim/dist/index.js";
-import { goMod, petSrc } from "./fixture.js";
-
-// The shim logs every path it touches unless told otherwise.
-const { debug } = await import("./node_modules/@bjorn3/browser_wasi_shim/dist/debug.js");
-debug.enable(false);
-
-const enc = new TextEncoder();
-const file = (s) => new File(enc.encode(s));
-
-const bytes = await readFile("./genspec-wasi.wasm");
-let t = performance.now();
-const module = await WebAssembly.compile(bytes);
-const compileMs = performance.now() - t;
-
-// bigTree builds a module with many packages, so the scan runs long enough for Go's scheduler to
-// idle - which is the only thing that makes it call poll_oneoff, and therefore the only thing that
-// makes the shim's busy-wait matter.
-function bigTree(pkgs, typesPer) {
-  const entries = [["go.mod", file(goMod)]];
-  for (let p = 0; p < pkgs; p++) {
-    const files = new Map();
-    for (let f = 0; f < 3; f++) {
-      let src = `package p${p}\n\n`;
-      for (let t = 0; t < typesPer; t++) {
-        src += `// T${f}_${t} is a model.\n//\n// swagger:model t${p}_${f}_${t}\ntype T${f}_${t} struct {\n` +
-          `\tID int64 \`json:"id"\`\n\tName string \`json:"name"\`\n\tTags []string \`json:"tags"\`\n}\n\n`;
-      }
-      files.set(`f${f}.go`, file(src));
-    }
-    entries.push([`p${p}`, new Directory(files)]);
-  }
-
-  return new PreopenDirectory("/src", new Map(entries));
-}
-
-async function run(label, extraArgs, tree) {
-  let out = "", err = "";
-  const fds = [
-    new OpenFile(new File([])),
-    ConsoleStdout.lineBuffered((l) => { out += l + "\n"; }),
-    ConsoleStdout.lineBuffered((l) => { err += l + "\n"; }),
-    tree || new PreopenDirectory("/src", new Map([
-      ["go.mod", file(goMod)],
-      ["models", new Directory(new Map([["pet.go", file(petSrc)]]))],
-    ])),
-  ];
-
-  const args = ["genspec-wasi.wasm", "-loader=own", "-goos", "linux", "-goarch", "amd64",
-    ...extraArgs, "-workdir", "/src", "./..."];
-  const wasi = new WASI(args, [], fds);
-
-  let pollCalls = 0, pollMs = 0, maxPollMs = 0;
-  const imports = wasi.wasiImport;
-  const rawPoll = imports.poll_oneoff.bind(imports);
-  imports.poll_oneoff = (...a) => {
-    const t0 = performance.now();
-    const r = rawPoll(...a);
-    const d = performance.now() - t0;
-    pollMs += d; maxPollMs = Math.max(maxPollMs, d); pollCalls++;
-
-    return r;
-  };
-
-  t = performance.now();
-  const instance = await WebAssembly.instantiate(module, { wasi_snapshot_preview1: imports });
-  const instantiateMs = performance.now() - t;
-
-  t = performance.now();
-  const code = wasi.start(instance);
-  const runMs = performance.now() - t;
-
-  let defs = null;
-  try { defs = Object.keys(JSON.parse(out).definitions || {}); } catch { /* not JSON */ }
-
-  console.log(`--- ${label} ---`);
-  console.log(`  exit=${code}  instantiate=${instantiateMs.toFixed(1)}ms  run=${runMs.toFixed(0)}ms`);
-  console.log(`  poll_oneoff: ${pollCalls} calls, ${pollMs.toFixed(1)}ms total, ${maxPollMs.toFixed(1)}ms worst`);
-  console.log(`  definitions: ${defs ? defs.length : "STDOUT WAS NOT JSON"}`);
-  if (err.trim()) console.log(`  stderr: ${err.trim().split("\n").slice(0, 3).join(" | ")}`);
-
-  return { defs, out };
-}
-
-console.log(`compile: ${compileMs.toFixed(0)}ms for ${(bytes.length / 1048576).toFixed(1)} MB\n`);
-const a = await run("small: 1 package", ["-stub-stdlib"]);
-const b = await run("small again, same compiled module", ["-stub-stdlib"]);
-await run("medium: 50 packages, 150 files, 750 models", ["-stub-stdlib"], bigTree(50, 5));
-await run("large: 200 packages, 600 files, 6000 models", ["-stub-stdlib"], bigTree(200, 10));
-console.log(`\nreuse of the compiled module across runs: ${JSON.stringify(a.defs) === JSON.stringify(b.defs) ? "OK" : "DIFFERENT"}`);
diff --git a/hack/browser/package-lock.json b/hack/browser/package-lock.json
deleted file mode 100644
index 70ae6b45..00000000
--- a/hack/browser/package-lock.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-  "name": "codescan-browser-probe",
-  "version": "0.0.0",
-  "lockfileVersion": 3,
-  "requires": true,
-  "packages": {
-    "": {
-      "name": "codescan-browser-probe",
-      "version": "0.0.0",
-      "dependencies": {
-        "@bjorn3/browser_wasi_shim": "^0.4.2"
-      }
-    },
-    "node_modules/@bjorn3/browser_wasi_shim": {
-      "version": "0.4.2",
-      "resolved": "https://registry.npmjs.org/@bjorn3/browser_wasi_shim/-/browser_wasi_shim-0.4.2.tgz",
-      "integrity": "sha512-/iHkCVUG3VbcbmEHn5iIUpIrh7a7WPiwZ3sHy4HZKZzBdSadwdddYDZAII2zBvQYV0Lfi8naZngPCN7WPHI/hA=="
-    }
-  }
-}
diff --git a/hack/browser/package.json b/hack/browser/package.json
deleted file mode 100644
index 29b86701..00000000
--- a/hack/browser/package.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "name": "codescan-browser-probe",
-  "version": "0.0.0",
-  "private": true,
-  "type": "module",
-  "description": "Throwaway probe: does the WASI artifact run in a browser, and what does it cost?",
-  "scripts": {
-    "serve": "python3 -m http.server 8099"
-  },
-  "dependencies": {
-    "@bjorn3/browser_wasi_shim": "^0.4.2"
-  }
-}
diff --git a/hack/browser/probe.js b/hack/browser/probe.js
deleted file mode 100644
index be67151a..00000000
--- a/hack/browser/probe.js
+++ /dev/null
@@ -1,44 +0,0 @@
-const worker = new Worker("./worker.js", { type: "module" });
-const $ = (id) => document.getElementById(id);
-
-// The status line is assembled, not written as markup. Everything in it comes back from the
-// worker — an exit code, and definition names read out of whatever module was scanned — and
-// none of that has any business being parsed as HTML.
-const status = (cls, text) => {
-  const span = document.createElement("span");
-  span.className = cls;
-  span.textContent = text;
-  $("status").replaceChildren(span);
-};
-
-worker.onmessage = (e) => {
-  const r = e.data;
-  if (!r.ok) {
-    status("bad", "FAILED");
-    $("err").textContent = r.error;
-
-    return;
-  }
-
-  const defs = (() => {
-    try { return Object.keys(JSON.parse(r.out).definitions || {}); } catch { return null; }
-  })();
-
-  status(
-    defs ? "ok" : "bad",
-    defs
-      ? `ran, exit ${r.code}, definitions: ${defs.join(", ") || "(none)"}`
-      : `ran, exit ${r.code}, but stdout was not JSON`,
-  );
-
-  $("timings").textContent = [
-    `compile      ${r.compileMs.toFixed(0)} ms   (once; reusable across runs)`,
-    `instantiate  ${r.instantiateMs.toFixed(1)} ms`,
-    `run          ${r.runMs.toFixed(0)} ms`,
-    `poll_oneoff  ${r.pollCalls} calls, ${r.pollMs.toFixed(1)} ms spinning`,
-  ].join("\n");
-  $("out").textContent = r.out || "(empty)";
-  $("err").textContent = r.err || "(empty)";
-};
-
-worker.postMessage({ url: "./genspec-wasi.wasm" });
diff --git a/hack/browser/worker.js b/hack/browser/worker.js
deleted file mode 100644
index 252d3acd..00000000
--- a/hack/browser/worker.js
+++ /dev/null
@@ -1,84 +0,0 @@
-// Runs one scan and reports what it cost.
-//
-// In a worker for two reasons: a scan is seconds of solid CPU, and the shim's poll_oneoff busy-waits
-// rather than yielding, so on the main thread it would stall the page outright.
-
-import {
-  WASI, File, Directory, OpenFile, ConsoleStdout, PreopenDirectory,
-} from "./node_modules/@bjorn3/browser_wasi_shim/dist/index.js";
-import { goMod, petSrc } from "./fixture.js";
-
-const enc = new TextEncoder();
-const file = (s) => new File(enc.encode(s));
-
-// Compiling a 14 MB module is the expensive part and the result is reusable: proc_exit ends an
-// instance for good, so every run needs a fresh instance but not a fresh compile.
-let compiled = null;
-let compileMs = 0;
-
-async function moduleOnce(url) {
-  if (compiled) return compiled;
-  const t0 = performance.now();
-  compiled = await WebAssembly.compileStreaming(fetch(url));
-  compileMs = performance.now() - t0;
-
-  return compiled;
-}
-
-function guestTree() {
-  return new PreopenDirectory("/src", new Map([
-    ["go.mod", file(goMod)],
-    ["models", new Directory(new Map([["pet.go", file(petSrc)]]))],
-  ]));
-}
-
-async function run(url) {
-  const module = await moduleOnce(url);
-
-  let out = "", err = "";
-  const fds = [
-    new OpenFile(new File([])),
-    ConsoleStdout.lineBuffered((l) => { out += l + "\n"; }),
-    ConsoleStdout.lineBuffered((l) => { err += l + "\n"; }),
-    guestTree(),
-  ];
-
-  const args = [
-    "genspec-wasi.wasm", "-loader=own", "-stub-stdlib",
-    "-goos", "linux", "-goarch", "amd64",
-    "-workdir", "/src", "./...",
-  ];
-  const wasi = new WASI(args, [], fds);
-
-  // The shim spins inside poll_oneoff for the whole requested delay. Measure it: if Go asks for real
-  // sleeps this is dead wall-clock, and it is the one thing that would rule this shim out.
-  let pollCalls = 0, pollMs = 0;
-  const imports = wasi.wasiImport;
-  const rawPoll = imports.poll_oneoff.bind(imports);
-  imports.poll_oneoff = (...a) => {
-    const t = performance.now();
-    const r = rawPoll(...a);
-    pollMs += performance.now() - t;
-    pollCalls++;
-
-    return r;
-  };
-
-  const t1 = performance.now();
-  const instance = await WebAssembly.instantiate(module, { wasi_snapshot_preview1: imports });
-  const instantiateMs = performance.now() - t1;
-
-  const t2 = performance.now();
-  const code = wasi.start(instance);
-  const runMs = performance.now() - t2;
-
-  return { out, err, code, compileMs, instantiateMs, runMs, pollCalls, pollMs };
-}
-
-self.onmessage = async (e) => {
-  try {
-    self.postMessage({ ok: true, ...(await run(e.data.url)) });
-  } catch (ex) {
-    self.postMessage({ ok: false, error: String(ex && ex.stack || ex) });
-  }
-};