// wasm/tests/parity-cross-tier.mjs // For every CORPUS entry, run the source on every tier (native python, // c-wasm, asm-wasm) and report mismatches against the expected output. // Known divergences (KNOWN_DIVERGE) are tolerated but printed in yellow // so the gap is visible without breaking the build. // // Python tier runs via the host's python3 lumbda.py because the Pyodide // loader needs a browser context. That keeps this test fast (no 10MB // CDN download) while still pinning behavior to the reference tier. import fs from "node:fs"; import path from "node:path"; import { execFileSync } from "node:child_process"; import { fileURLToPath } from "node:url"; import { CORPUS, KNOWN_DIVERGE } from "./parity-corpus.mjs"; const here = path.dirname(fileURLToPath(import.meta.url)); const dist = path.resolve(here, "..", "dist"); const repoRoot = path.resolve(here, "..", ".."); const state = { ok: 0, fail: 0, knownDiverge: 0, failures: [] }; function check(tag, tier, expected, got, isKnown) { const passed = got === expected; if (passed) state.ok++; else if (isKnown) state.knownDiverge++; else { state.fail++; state.failures.push({ tag, tier, expected, got }); } const status = passed ? "✓" : isKnown ? "~" // known divergence : "✗"; const label = `${status} ${tier.padEnd(6)} ${tag}`; if (!passed) { const detail = `expected ${JSON.stringify(expected)} · got ${JSON.stringify(got)}`; console.log(` ${label} — ${detail}`); } } // Wrap each test source so the result is printed via display (uniform // across tiers — no write quoting, no "is this the last expression" // behavior to depend on). The source may contain multiple top-level // forms (e.g. defines), so we put the final expression in a (begin ...) // and display its result. function wrap(src) { // The source's last sub-expression is what we want to display. // We rely on the tiers each evaluating the lot and `(display ...)` // emitting display semantics on the value. return `(display (begin ${src})) (newline)`; } // ─── tier adapters ───────────────────────────────────────────────── function nativePython(src) { const tmp = `/tmp/parity-${process.pid}.lsp`; fs.writeFileSync(tmp, wrap(src) + "\n"); try { return execFileSync("python3", [path.join(repoRoot, "lumbda.py"), tmp], { encoding: "utf8", timeout: 30000, }).trim(); } finally { try { fs.unlinkSync(tmp); } catch {} } } async function withAsmTier() { const wasmBytes = fs.readFileSync(path.join(dist, "asm", "lumbda-asm.wasm")); const importObj = { env: { bend_call() { return 0; }, emit_chunk() {} } }; const { instance } = await WebAssembly.instantiate(wasmBytes, importObj); const exp = instance.exports; exp.lumbda_init(); return (src) => { const bytes = new TextEncoder().encode(wrap(src)); new Uint8Array(exp.memory.buffer).set(bytes, exp.lumbda_source_ptr()); try { exp.lumbda_eval(bytes.length); } catch (e) { return `THREW: ${e.message}`; } return new TextDecoder().decode( new Uint8Array(exp.memory.buffer, exp.lumbda_output_ptr(), exp.lumbda_output_len()) ).trim(); }; } async function withCTier() { const factoryURL = "file://" + path.join(dist, "c", "lumbda-c.js"); const createLumbdaC = (await import(factoryURL)).default; let out = []; const m = await createLumbdaC({ locateFile: (p) => path.join(dist, "c", p), print: (line) => out.push(line), printErr: () => {}, }); m.cwrap("lumbda_wasm_init", null, [])(); const _eval = m.cwrap("lumbda_wasm_eval", "number", ["string"]); const _free = m.cwrap("lumbda_wasm_free_result", null, ["number"]); return (src) => { out = []; const r = _eval(wrap(src)); if (r) { const msg = m.UTF8ToString(r); _free(r); return msg; } return out.join("\n").trim(); }; } (async () => { console.log("─".repeat(70)); console.log("Cross-tier parity probe — python (ref) vs c-wasm vs asm-wasm"); console.log("─".repeat(70)); const runAsm = await withAsmTier(); const runC = await withCTier(); for (const entry of CORPUS) { const knownOn = KNOWN_DIVERGE[entry.tag] || []; const isAsmKnown = knownOn.includes("asm"); const isCKnown = knownOn.includes("c"); const isPyKnown = knownOn.includes("python"); const py = nativePython(entry.src); const cw = runC(entry.src); const aw = runAsm(entry.src); check(entry.tag, "py-ref", entry.expected, py, isPyKnown); check(entry.tag, "c-wasm", entry.expected, cw, isCKnown); check(entry.tag, "asm", entry.expected, aw, isAsmKnown); } console.log("─".repeat(70)); console.log(` passing : ${state.ok}`); console.log(` known diverge: ${state.knownDiverge} (tracked in KNOWN_DIVERGE)`); console.log(` fail : ${state.fail}`); if (state.fail > 0) { console.log("\nUnknown divergences (fix or add to KNOWN_DIVERGE):"); for (const f of state.failures) { console.log(` ${f.tier} ${f.tag}: expected ${JSON.stringify(f.expected)} got ${JSON.stringify(f.got)}`); } } process.exit(state.fail ? 1 : 0); })();