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]>
90 lines
3.6 KiB
JavaScript
90 lines
3.6 KiB
JavaScript
// cases.mjs: the module corpus (corpus.json) read into the bytes a sink passes, and the
|
|
// checks every runner makes of what came back. For Node; no dependency.
|
|
//
|
|
// A case's payload is a path below wai/, or {"hex"}; its set is null, one file, {"external"}
|
|
// (a file the corpus names by digest and does not hold, read from the --external
|
|
// directory), or a list of [name, file] pairs passed as waism §5's file sequence: for each
|
|
// file in order, the name's length in one byte, the name, the file's length in four bytes
|
|
// little-endian, and the file.
|
|
|
|
import { createHash } from 'node:crypto';
|
|
import { readFileSync } from 'node:fs';
|
|
import { dirname, join, resolve } from 'node:path';
|
|
|
|
export function sha256(b) {
|
|
return createHash('sha256').update(b).digest('hex');
|
|
}
|
|
|
|
function file(v, wai, external) {
|
|
if (typeof v === 'string') return new Uint8Array(readFileSync(join(wai, v)));
|
|
if (v && typeof v.hex === 'string') return new Uint8Array(Buffer.from(v.hex, 'hex'));
|
|
if (v && typeof v.file === 'string') return new Uint8Array(readFileSync(join(wai, v.file)));
|
|
if (v && typeof v.external === 'string') {
|
|
if (!external) throw new Error(`${v.external} is an external file, and no --external directory was given`);
|
|
return new Uint8Array(readFileSync(join(external, v.external)));
|
|
}
|
|
throw new Error(`not a file: ${JSON.stringify(v)}`);
|
|
}
|
|
|
|
function set(v, wai, external) {
|
|
if (v === null) return new Uint8Array(0);
|
|
if (!Array.isArray(v)) return file(v, wai, external);
|
|
const parts = [];
|
|
for (const [name, what] of v) {
|
|
const b = file(what, wai, external);
|
|
const n = Buffer.from(name, 'utf8');
|
|
if (n.length < 1 || n.length > 255) throw new Error(`a name of ${n.length} bytes`);
|
|
const len = Buffer.alloc(4);
|
|
len.writeUInt32LE(b.length);
|
|
parts.push(Buffer.from([n.length]), n, len, b);
|
|
}
|
|
return new Uint8Array(Buffer.concat(parts));
|
|
}
|
|
|
|
/** The cases of a corpus.json, with their bytes. */
|
|
export function loadCases(corpusPath, { external = null, only = null } = {}) {
|
|
const doc = JSON.parse(readFileSync(corpusPath, 'utf8'));
|
|
if (doc.format !== 'waism-module-corpus/1') throw new Error(`${corpusPath}: not a waism-module-corpus/1`);
|
|
const wai = dirname(dirname(resolve(corpusPath)));
|
|
const cases = [];
|
|
for (const c of doc.cases) {
|
|
if (only && !c.name.startsWith(only)) continue;
|
|
try {
|
|
cases.push({ name: c.name, cap: c.capability, payload: file(c.payload, wai, external), set: set(c.set, wai, external), expect: c.expect });
|
|
} catch (e) {
|
|
throw new Error(`${c.name}: ${e.message}`);
|
|
}
|
|
}
|
|
return cases;
|
|
}
|
|
|
|
/** A result as one line of the run digest: sha256:<hex>, refuse:<code> or sink:<code>. */
|
|
export function outcomeLine(r) {
|
|
if (r.output) return `sha256:${sha256(r.output)}`;
|
|
if (r.refused !== undefined) return `refuse:${r.refused}`;
|
|
return `sink:${r.sink}`;
|
|
}
|
|
|
|
/** Whether a result is the one the case states. */
|
|
export function holds(c, r) {
|
|
if (c.expect.sha256 !== undefined) return !!r.output && sha256(r.output) === c.expect.sha256;
|
|
return r.refused === c.expect.refuse;
|
|
}
|
|
|
|
/** The check of one run: each case its corpus outcome, and the run digest. */
|
|
export function judge(cases, results) {
|
|
const lines = [];
|
|
let wrong = 0;
|
|
const report = [];
|
|
cases.forEach((c, i) => {
|
|
const r = results[i];
|
|
const line = outcomeLine(r);
|
|
lines.push(`${c.name} ${line}\n`);
|
|
if (!holds(c, r)) {
|
|
wrong++;
|
|
const want = c.expect.sha256 !== undefined ? `sha256:${c.expect.sha256}` : `refuse:${c.expect.refuse}`;
|
|
report.push(` ${c.name}: ${line}${r.detail ? ` (${r.detail})` : ''}, not ${want}`);
|
|
}
|
|
});
|
|
return { wrong, report, digest: sha256(Buffer.from(lines.join(''), 'utf8')) };
|
|
}
|