Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
d233441
fix: auto-save uncommitted implementation work (kad-165.2, gt-pvx saf…
brandonpayton Jun 12, 2026
a8f2897
fix: auto-save uncommitted implementation work (kad-165.3, gt-pvx saf…
brandonpayton Jun 13, 2026
9fab588
fix: handle no-slow SpiderMonkey chunks (kad-165.6)
brandonpayton Jun 13, 2026
3e60fe1
fix: retry browser SpiderMonkey page resets (kad-t2n)
brandonpayton Jun 13, 2026
73f4867
fix: reuse browser page reset predicate (kad-t2n)
brandonpayton Jun 13, 2026
f51ab82
fix: recover browser SpiderMonkey guest timeouts (kad-bz8)
brandonpayton Jun 13, 2026
425ac7e
fix: skip browser SpiderMonkey 64-bit atomics jit-tests (kad-e6d)
brandonpayton Jun 13, 2026
4273fee
fix: clean up browser spidermonkey bridge vite process (kad-219)
brandonpayton Jun 13, 2026
1cba72b
fix: skip browser BigInt Atomics jstests (kad-165.12)
brandonpayton Jun 13, 2026
1ca9d7f
fix: recycle browser spidermonkey bridge pages (kad-y37)
brandonpayton Jun 13, 2026
a6012bb
test: preserve SpiderMonkey Node jit-test artifacts (kad-165.5)
brandonpayton Jun 14, 2026
64f0a34
fix: harden SpiderMonkey official runner resumes (kad-165.4)
brandonpayton Jun 13, 2026
ed5d8d1
fix: guard SpiderMonkey runner empty skip arrays (kad-165.4)
brandonpayton Jun 13, 2026
37348da
Merge remote-tracking branch 'origin/polecat/toast/kad-165.4@mqbm52l5…
brandonpayton Jun 14, 2026
e59ebc6
fix: avoid eager SpiderMonkey wasm resolution (kad-165.7)
brandonpayton Jun 13, 2026
00efb44
fix: harden SpiderMonkey known-skip queueing (kad-165.7)
brandonpayton Jun 13, 2026
ebfd466
test: mark browser SpiderMonkey staging outliers known skips (kad-165.7)
brandonpayton Jun 14, 2026
81c86c4
fix: harden run-example exec resolution (kad-165.7)
brandonpayton Jun 14, 2026
4d0999a
Merge remote-tracking branch 'origin/polecat/rictus/kad-165.7@mqbnl5l…
brandonpayton Jun 14, 2026
7124392
docs: classify Node SpiderMonkey jstest timeouts (kad-165.19)
brandonpayton Jun 14, 2026
e6b0790
Merge remote-tracking branch 'origin/polecat/furiosa/kad-165.19@mqef5…
brandonpayton Jun 15, 2026
314d1b0
fix: classify SpiderMonkey recursion stress skips (kad-165.20)
brandonpayton Jun 14, 2026
bfc45ea
fix: classify Node BigInt Atomics jstests (kad-165.18)
brandonpayton Jun 14, 2026
ddc5567
fix: capture Node SpiderMonkey bridge probe output (kad-165.18)
brandonpayton Jun 15, 2026
3c14493
test: classify Node SpiderMonkey jstest resource timeouts (kad-165.21)
brandonpayton Jun 14, 2026
29e1003
Merge remote-tracking branch 'origin/polecat/dag/kad-165.18@mqeeqkmd'…
brandonpayton Jun 15, 2026
909c527
Merge remote-tracking branch 'origin/polecat/cheedo/kad-165.21@mqefza…
brandonpayton Jun 15, 2026
ab7af9f
Merge remote-tracking branch 'origin/polecat/dementus/kad-165.20@mqef…
brandonpayton Jun 15, 2026
57a4d75
docs: classify SpiderMonkey suite failures (kad-165.15)
brandonpayton Jun 14, 2026
6ce5803
test: classify raise-race as flaky (kad-165.22)
brandonpayton Jun 15, 2026
de2f6c0
test: classify raise-race as flaky (kad-165.22)
brandonpayton Jun 15, 2026
0a5bee0
Merge remote-tracking branch 'origin/polecat/immortan/kad-165.15@mqe8…
brandonpayton Jun 15, 2026
ad1c2cd
docs: summarize SpiderMonkey epic results (kad-165.10)
brandonpayton Jun 15, 2026
b42458f
Merge origin/main into SpiderMonkey synthesis
brandonpayton Jun 15, 2026
366d243
Merge remote-tracking branch 'origin/polecat/bullet/kad-165.22@mqejxu…
brandonpayton Jun 15, 2026
7d25544
fix: mount WordPress boot test checkout (kad-luz)
brandonpayton Jun 15, 2026
b5dd6ee
Merge SpiderMonkey integration target into synthesis
brandonpayton Jun 15, 2026
073e436
Merge remote-tracking branch 'origin/polecat/capable/kad-165.10@mqekf…
brandonpayton Jun 15, 2026
e71b8f5
docs: post SpiderMonkey hard test counts (kad-165.14)
brandonpayton Jun 15, 2026
75dbf3f
Merge remote-tracking branch 'origin/polecat/angharad/kad-165.14@mqel…
brandonpayton Jun 15, 2026
721a15e
Merge remote-tracking branch 'origin/polecat/cheedo/kad-luz@mqekppfu'…
brandonpayton Jun 15, 2026
37c980c
test: classify browser SpiderMonkey Promise stack stress (kad-6wx)
brandonpayton Jun 14, 2026
4c5c1ba
Merge remote-tracking branch 'origin/polecat/nux/kad-6wx@mqef6yby' in…
brandonpayton Jun 15, 2026
0f36b47
Avoid mozglue env interposers for wasm SpiderMonkey
brandonpayton Jun 19, 2026
a1e7a46
Merge remote-tracking branch 'origin/main' into kd-vsh-pr697-date
brandonpayton Jun 19, 2026
0e6e53b
Classify browser SpiderMonkey recursion stack stress
brandonpayton Jun 19, 2026
2d8e477
Classify more browser SpiderMonkey stack stress tests
brandonpayton Jun 19, 2026
be38163
Classify PR697 browser regress stack stress
brandonpayton Jun 19, 2026
a482c7c
Add browser SpiderMonkey sharded runner
brandonpayton Jun 19, 2026
af33b8e
Retry browser SpiderMonkey OOB traps when requested
brandonpayton Jun 20, 2026
fba4cdd
Prevent abandoned SpiderMonkey bridge requests from wedging browser l…
brandonpayton Jun 20, 2026
4f54ac6
Recycle browser shell on SpiderMonkey OOB pressure
brandonpayton Jun 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ docs-site/.vitepress/dist/
# Browser example build output
apps/browser-demos/dist/

# Long-running external suite logs. Harness summaries belong in PR/docs;
# raw logs are local artifacts.
/test-results/

# Per-package source/build trees produced by packages/registry/*/build-*.sh.
# These are recreated on every build (clone or untar+make).
packages/registry/*/*-src/
Expand Down
11 changes: 11 additions & 0 deletions apps/browser-demos/pages/spidermonkey-test/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>SpiderMonkey Official Test Runner</title>
</head>
<body>
<div id="status">Loading SpiderMonkey test image...</div>
<script type="module" src="./main.ts"></script>
</body>
</html>
347 changes: 347 additions & 0 deletions apps/browser-demos/pages/spidermonkey-test/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,347 @@
/**
* Browser-side SpiderMonkey shell runner.
*
* The Node/Playwright bridge calls window.__runSpiderMonkeyScript(...) for
* every upstream harness invocation. For official jstests/jit-tests we keep a
* single BrowserKernel alive and spawn /usr/bin/js from the prebuilt VFS image
* so thousands of shell invocations do not need to reload the whole image.
*/
import { BrowserKernel } from "@host/browser-kernel-host";
import { MemoryFileSystem } from "@host/vfs/memory-fs";
import { ensureDirRecursive, writeVfsFile } from "@host/vfs/image-helpers";
import kernelWasmUrl from "@kernel-wasm?url";

interface RunSpiderMonkeyRequest {
source?: string;
shellArgs?: string[];
argv?: string[];
scriptPath?: string;
scriptContent?: string;
scriptArgs?: string[];
timeoutMs?: number;
}

interface RunSpiderMonkeyResult {
exitCode: number;
stdout: string;
stderr: string;
error?: string;
durationMs: number;
processReaped?: boolean;
}

declare global {
interface Window {
__spiderMonkeyTestReady: boolean;
__runSpiderMonkeyScript: (
request: RunSpiderMonkeyRequest,
) => Promise<RunSpiderMonkeyResult>;
}
}

let kernelBytes: ArrayBuffer | null = null;
let vfsImageBytes: Uint8Array | null = null;
let jsBytes: ArrayBuffer | null = null;
let officialFs: MemoryFileSystem | null = null;
let officialKernel: BrowserKernel | null = null;
let officialStdout = "";
let officialStderr = "";
let jsMaxMemoryPages: number | undefined;
const defaultThreadSlots = Number(
new URLSearchParams(window.location.search).get("threadSlots") ?? "64",
);

function readVarU32(bytes: Uint8Array, offset: { value: number }): number {
let result = 0;
let shift = 0;
for (;;) {
if (offset.value >= bytes.length) throw new Error("truncated wasm varuint32");
const byte = bytes[offset.value++];
result |= (byte & 0x7f) << shift;
if ((byte & 0x80) === 0) return result >>> 0;
shift += 7;
if (shift > 35) throw new Error("invalid wasm varuint32");
}
}

function skipName(bytes: Uint8Array, offset: { value: number }): void {
const length = readVarU32(bytes, offset);
offset.value += length;
if (offset.value > bytes.length) throw new Error("truncated wasm name");
}

function readMemoryMaximumPages(bytes: Uint8Array, offset: { value: number }): number | undefined {
const flags = readVarU32(bytes, offset);
readVarU32(bytes, offset); // minimum
if ((flags & 0x1) === 0) return undefined;
return readVarU32(bytes, offset);
}

function detectWasmMaximumMemoryPages(wasmBytes: ArrayBuffer): number | undefined {
const bytes = new Uint8Array(wasmBytes);
if (
bytes.length < 8 ||
bytes[0] !== 0x00 ||
bytes[1] !== 0x61 ||
bytes[2] !== 0x73 ||
bytes[3] !== 0x6d
) {
return undefined;
}

let pos = 8;
while (pos < bytes.length) {
const sectionId = bytes[pos++];
const sectionSizeOffset = { value: pos };
const sectionSize = readVarU32(bytes, sectionSizeOffset);
const sectionStart = sectionSizeOffset.value;
const sectionEnd = sectionStart + sectionSize;
if (sectionEnd > bytes.length) return undefined;

const offset = { value: sectionStart };
if (sectionId === 2) {
const count = readVarU32(bytes, offset);
for (let i = 0; i < count; i++) {
skipName(bytes, offset);
skipName(bytes, offset);
const kind = bytes[offset.value++];
switch (kind) {
case 0x00:
readVarU32(bytes, offset);
break;
case 0x01: {
offset.value++;
const flags = readVarU32(bytes, offset);
readVarU32(bytes, offset);
if ((flags & 0x1) !== 0) readVarU32(bytes, offset);
break;
}
case 0x02:
return readMemoryMaximumPages(bytes, offset);
case 0x03:
offset.value += 2;
break;
default:
return undefined;
}
}
} else if (sectionId === 5) {
const count = readVarU32(bytes, offset);
if (count > 0) return readMemoryMaximumPages(bytes, offset);
}
pos = sectionEnd;
}
return undefined;
}

function readVfsFile(fs: MemoryFileSystem, path: string): Uint8Array {
const st = fs.stat(path);
const fd = fs.open(path, 0, 0);
try {
const out = new Uint8Array(st.size);
let offset = 0;
while (offset < out.length) {
const n = fs.read(fd, out.subarray(offset), null, out.length - offset);
if (n <= 0) break;
offset += n;
}
return out.slice(0, offset);
} finally {
fs.close(fd);
}
}

function createFs(): MemoryFileSystem {
if (!vfsImageBytes) throw new Error("SpiderMonkey test VFS image not loaded");
return MemoryFileSystem.fromImage(vfsImageBytes, {
maxByteLength: 1536 * 1024 * 1024,
});
}

function ensureParent(fs: MemoryFileSystem, path: string): void {
const slash = path.lastIndexOf("/");
if (slash > 0) ensureDirRecursive(fs, path.slice(0, slash));
}

function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
return Promise.race([
promise,
new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error("TIMEOUT")), timeoutMs),
),
]);
}

async function init() {
const [kernelBuf, imageBuf] = await Promise.all([
fetch(kernelWasmUrl).then((r) => {
if (!r.ok) throw new Error(`kernel fetch failed: ${r.status}`);
return r.arrayBuffer();
}),
fetch("/spidermonkey-test.vfs.zst").then((r) => {
if (!r.ok) {
throw new Error(
`spidermonkey-test.vfs.zst not found (${r.status}). ` +
"Run: bash images/vfs/scripts/build-spidermonkey-test-vfs-image.sh",
);
}
return r.arrayBuffer();
}),
]);

kernelBytes = kernelBuf;
vfsImageBytes = new Uint8Array(imageBuf);
const fs = createFs();
const js = readVfsFile(fs, "/usr/bin/js");
jsBytes = new ArrayBuffer(js.byteLength);
new Uint8Array(jsBytes).set(js);
jsMaxMemoryPages = detectWasmMaximumMemoryPages(jsBytes);

async function getOfficialKernel(): Promise<BrowserKernel> {
if (officialKernel) return officialKernel;
if (!officialFs) officialFs = createFs();
officialKernel = new BrowserKernel({
memfs: officialFs,
maxWorkers: 8,
defaultThreadSlots,
maxMemoryPages: jsMaxMemoryPages,
onStdout: (data) => {
officialStdout += new TextDecoder().decode(data);
},
onStderr: (data) => {
officialStderr += new TextDecoder().decode(data);
},
});
await officialKernel.init(kernelBytes!);
return officialKernel;
}

window.__runSpiderMonkeyScript = async (
request: RunSpiderMonkeyRequest,
): Promise<RunSpiderMonkeyResult> => {
const start = performance.now();

if (request.argv) {
officialStdout = "";
officialStderr = "";
const argv = ["/usr/bin/js", ...request.argv];
let pid: number | undefined;
try {
const kernel = await getOfficialKernel();
const spawned = await kernel.spawnFromVfs("/usr/bin/js", argv, {
cwd: "/tmp",
env: [
"HOME=/tmp",
"TMPDIR=/tmp",
"PATH=/usr/bin:/bin",
],
});
pid = spawned.pid;
const exitCode = await withTimeout(
spawned.exit,
request.timeoutMs ?? 60_000,
);
const processReaped = (await kernel.readProcMaps(pid)) === null;
return {
exitCode,
stdout: officialStdout,
stderr: officialStderr,
durationMs: Math.round(performance.now() - start),
processReaped,
};
} catch (err: any) {
const message = err?.message || String(err);
let processReaped: boolean | undefined;
if (message.includes("TIMEOUT") && pid !== undefined && officialKernel) {
await officialKernel.terminateProcess(pid, -1).catch(() => {});
processReaped = (await officialKernel.readProcMaps(pid).catch(() => null)) === null;
}
return {
exitCode: -1,
stdout: officialStdout,
stderr: officialStderr,
error: message.includes("TIMEOUT") ? "TIMEOUT" : message,
durationMs: Math.round(performance.now() - start),
processReaped,
};
}
}

const fsForRun = createFs();
let argv: string[];
if (request.scriptPath) {
if (request.scriptContent !== undefined) {
ensureParent(fsForRun, request.scriptPath);
writeVfsFile(fsForRun, request.scriptPath, request.scriptContent, 0o644);
}
argv = [
"/usr/bin/js",
...(request.shellArgs ?? []),
request.scriptPath,
...(request.scriptArgs ?? []),
];
} else {
argv = [
"/usr/bin/js",
...(request.shellArgs ?? []),
"-e",
request.source ?? "",
];
}

let stdout = "";
let stderr = "";
const kernel = new BrowserKernel({
memfs: fsForRun,
maxWorkers: 8,
defaultThreadSlots,
maxMemoryPages: jsMaxMemoryPages,
onStdout: (data) => {
stdout += new TextDecoder().decode(data);
},
onStderr: (data) => {
stderr += new TextDecoder().decode(data);
},
});

try {
await kernel.init(kernelBytes!);
const exitCode = await withTimeout(
kernel.spawn(jsBytes!, argv, {
cwd: "/tmp",
env: [
"HOME=/tmp",
"TMPDIR=/tmp",
"PATH=/usr/bin:/bin",
],
}),
request.timeoutMs ?? 60_000,
);
return {
exitCode,
stdout,
stderr,
durationMs: Math.round(performance.now() - start),
};
} catch (err: any) {
const message = err?.message || String(err);
return {
exitCode: -1,
stdout,
stderr,
error: message.includes("TIMEOUT") ? "TIMEOUT" : message,
durationMs: Math.round(performance.now() - start),
};
} finally {
await kernel.destroy().catch(() => {});
}
};

window.__spiderMonkeyTestReady = true;
document.getElementById("status")!.textContent = "Ready";
}

init().catch((err) => {
console.error(err);
document.getElementById("status")!.textContent = `Error: ${err?.message || err}`;
});
7 changes: 7 additions & 0 deletions apps/browser-demos/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,12 @@ const defaultDemoInputs = {

const demoInputs = {
...defaultDemoInputs,
benchmark: path.resolve(__dirname, "pages/benchmark/index.html"),
"git-test": path.resolve(__dirname, "pages/git-test/index.html"),
"mariadb-test": path.resolve(__dirname, "pages/mariadb-test/index.html"),
"sqlite-test": path.resolve(__dirname, "pages/sqlite-test/index.html"),
"spidermonkey-test": path.resolve(__dirname, "pages/spidermonkey-test/index.html"),
"test-runner": path.resolve(__dirname, "pages/test-runner/index.html"),
// The perl, python, ruby, erlang, texlive, and redis package entries
// are not bundled into this static build while their slow builds
// live in kandelo-software. The root gallery fetches that
Expand All @@ -429,9 +434,11 @@ function selectedDemoInputs(): typeof demoInputs | Record<string, string> {
}

const disableBrowserTestHmr = process.env.KANDELO_BROWSER_TEST_NO_HMR === "1";
const browserTestViteCacheDir = process.env.KANDELO_BROWSER_TEST_VITE_CACHE_DIR?.trim();

export default defineConfig({
base: process.env.VITE_BASE || "/",
cacheDir: browserTestViteCacheDir || undefined,
resolve: {
alias: {
"@host": path.resolve(repoRoot, "host/src"),
Expand Down
Loading
Loading