open-standards/wai/wai-int-module/run-node.mjs
Transaction Science 98d646dc92 wai-int-module: the first WA²ISM module of record, the operator-set module held at the integer level on every engine
wai/wai-int-module is one WebAssembly module with the interface of waism §5 and nothing else:
no import, one memory with a declared maximum, and every allocation trapping where memory
cannot grow (R13). It decodes the integer payloads (WIV1, WIH1, WIA1, WIS1), int_synth,
int_mlicpp, opus.dred.int and avatar.face through wai-rs's integer decoders, whose networks
run on wai-ops/1; networks and weights arrive as each capability's set.

- record/: the scalar and SIMD128 builds (7,099 vector instructions), named by SHA-256 in
  manifest.json, each held at the integer level by the Rust checker and check-module.mjs.
  build.sh builds both with the machine's paths remapped and checks both with both checkers.
  A build on another host has other bytes (the compiler's crate hashes take in each path
  dependency's absolute path and the host) and gives every case the same outcome.
- corpus.json (corpus.py): every committed vector and refusal fixture of those
  capabilities, 120 cases, from the committed corpora.
- host/ (wasmtime 50.0.1 Cranelift and Winch, Wasmi 2.0.0 with floats off), run-node.mjs and
  browser/run-browser.mjs run every case as a sink does and print one run digest; every run,
  on Linux x86_64, Linux arm64 and macOS arm64, gives the same.
- plant.py plants a float or a relaxed vector instruction in a module, for the checkers.
- waism §5, The set: one file as its bytes, several as their file sequence. avatars §6 names
  generator.wfg and identity.wfi; avatars §7.8, opus-dred §4.8 and determinism-tiers §5 state
  the module as a fabric.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-08 16:45:56 -05:00

95 lines
3.8 KiB
JavaScript

// run-node.mjs: the module of record on Node's engine, as a sink runs it.
//
// node run-node.mjs [--external DIR] [--only PREFIX] [--limit SECONDS] CORPUS MODULE...
//
// For each module, every case of the module corpus (corpus.json) on an instance of its own,
// through sink.mjs (waism §5; §6 checks 11 to 13), in a worker that this script ends when one
// run passes the wall-clock limit (waism §7, `wall-clock`; default 300 seconds a case). Each
// output is held to the corpus, and each run prints the same line as the native host
// (host/): its module, engine, the cases it held and its run digest. Every run digest must
// be the same.
//
// Exit status 0 when every case of every run is the corpus's and every run digest is the
// same; 1 otherwise; 2 for a usage or input error.
import { readFileSync } from 'node:fs';
import { arch } from 'node:process';
import { Worker } from 'node:worker_threads';
import { judge, loadCases, sha256 } from './cases.mjs';
function usage(why) {
if (why) console.error(`run-node.mjs: ${why}`);
console.error('usage: node run-node.mjs [--external DIR] [--only PREFIX] [--limit SECONDS] CORPUS MODULE...');
process.exit(2);
}
const argv = process.argv.slice(2);
let external = null, only = null, limit = 300;
const rest = [];
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === '--external') external = argv[++i] ?? usage();
else if (a === '--only') only = argv[++i] ?? usage();
else if (a === '--limit') limit = Number(argv[++i] ?? usage());
else if (a.startsWith('--')) usage(`no option ${a}`);
else rest.push(a);
}
if (rest.length < 2 || !(limit > 0)) usage();
let cases;
try {
cases = loadCases(rest[0], { external, only });
} catch (e) {
console.error(`run-node.mjs: ${e.message}`);
process.exit(2);
}
/** Every case through one module in a worker, each under the limit. */
function runModule(moduleBytes) {
return new Promise((done) => {
const results = new Array(cases.length);
const w = new Worker(new URL('./node-worker.mjs', import.meta.url), { workerData: { moduleBytes, cases } });
let timer = null, current = -1;
const finish = () => { clearTimeout(timer); done(results); };
w.on('message', (m) => {
if (m.start !== undefined) {
current = m.start;
clearTimeout(timer);
timer = setTimeout(() => {
results[current] = { sink: 'module_timeout', detail: `past ${limit} s` };
for (let i = current + 1; i < cases.length; i++) results[i] = { sink: 'module_timeout', detail: 'not run: an earlier case timed out' };
w.terminate().then(finish);
}, limit * 1000);
} else if (m.done !== undefined) {
results[m.done] = m.r;
} else if (m.end) {
w.terminate().then(finish);
}
});
w.on('error', (e) => {
for (let i = Math.max(current, 0); i < cases.length; i++) results[i] ??= { sink: 'module_trap', detail: e.message };
finish();
});
});
}
const digests = new Map();
let failed = false;
const engine = `node@${process.versions.node}/v8@${process.versions.v8}`;
for (const path of rest.slice(1)) {
const bytes = new Uint8Array(readFileSync(path));
const t0 = Date.now();
const results = await runModule(bytes);
const { wrong, report, digest } = judge(cases, results);
for (const line of report) console.log(line);
console.log(`${path} (SHA-256 ${sha256(bytes)}) on ${engine}, ${arch}: ${cases.length - wrong} of ${cases.length} cases the corpus's; run digest ${digest}; ${((Date.now() - t0) / 1000).toFixed(1)} s`);
failed ||= wrong > 0;
digests.set(path, digest);
}
const distinct = new Set(digests.values());
if (distinct.size > 1) {
console.log(`run-node.mjs: the runs differ: ${distinct.size} run digests`);
failed = true;
} else {
console.log(`run-node.mjs: ${digests.size} runs, one run digest ${[...distinct][0]}`);
}
process.exit(failed ? 1 : 0);