A decode through the integer operator set lowers its set and loads each graph, the bounds pass included, on every call. int_set_cache::LoadedSets keeps a set once it has loaded, keyed by (capability, lowering, profile, SHA-256 of the listing of the members read), within a byte budget (256 MiB, LRU, 0 = off). int_payload::decode_int / declared_cost and int_synth::decode_from_spec take the load step from it; every other check runs as before. A refused set is never kept. wai_ops::Graph::heap_bytes counts an entry's graphs. Committed before the first build; results follow. Co-Authored-By: Claude Opus 5.5 <[email protected]>
92 lines
4.8 KiB
JavaScript
92 lines
4.8 KiB
JavaScript
// The wasm32 decode of the integer payloads (extensions/integer-payloads.md) equals
|
|
// the native decode: each committed WIV1, WIH1, WIA1 and WIS1 payload, decoded by
|
|
// decodeWiv1 / decodeWih1 / decodeWia1 / decodeWis1 in the browser crate built for
|
|
// wasm32, must give the SHA-256 its vector's sha256.json sidecar states, and a
|
|
// tool's JSON form must be refused with bad_magic. A second decode through the same
|
|
// set is answered from the page's store of loaded sets (loadedSetStats: one more hit,
|
|
// no more misses) with the same bytes, and a budget of 0 keeps and looks up nothing.
|
|
//
|
|
// node verify_int_payload_wasm.mjs [path/to/wai_web.js] [path/to/wai-rs/tests/vectors]
|
|
//
|
|
// Exit 0 iff every check passes.
|
|
|
|
import { readFileSync } from 'node:fs';
|
|
import { createHash } from 'node:crypto';
|
|
import { dirname, resolve } from 'node:path';
|
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const pkg = resolve(here, process.argv[2] ?? './pkg-node/wai_web.js');
|
|
const vectors = resolve(process.argv[3] ?? resolve(here, '../../wai-rs/tests/vectors'));
|
|
const wai = await import(pathToFileURL(pkg).href);
|
|
|
|
const rd = (rel) => new Uint8Array(readFileSync(`${vectors}/${rel}`));
|
|
const side = (rel, key) => JSON.parse(readFileSync(`${vectors}/${rel}`, 'utf8'))[key];
|
|
const sha = (b) => createHash('sha256').update(b).digest();
|
|
const hex = (b) => Buffer.from(b).toString('hex');
|
|
const clip = (v) => {
|
|
const n = v.width * v.height * 3, rgb = v.rgb, digests = [];
|
|
for (let f = 0; f < v.nFrames; f++) {
|
|
const one = v.frame(f);
|
|
if (one.length !== n || !one.every((b, i) => b === rgb[f * n + i])) throw new Error(`frame ${f} differs from rgb`);
|
|
digests.push(sha(one));
|
|
}
|
|
return hex(sha(Buffer.concat(digests)));
|
|
};
|
|
const [g, h, e] = [rd('int_hyper/tiny_g_s.bin'), rd('int_hyper/tiny_h_s.bin'), rd('int_hyper/tiny_entropy.json')];
|
|
|
|
const checks = [
|
|
['int_video (WIV1, keyframe 0)', () => clip(wai.decodeWiv1('wai.video.int_motion', rd('int_video/int_video_bitstream.wiv1'))),
|
|
side('int_video/sha256.json', 'int_video').clip_sha256],
|
|
['video_real (WIV1, half-pel)', () => clip(wai.decodeWiv1('wai.video.int_motion', rd('int_video/video_real_bitstream.wiv1'))),
|
|
side('int_video/sha256.json', 'video_real').clip_sha256],
|
|
['video_nk (WIV1, keyframe 1)', () => clip(wai.decodeWiv1('wai.video.int_hyper', rd('int_video/video_nk_bitstream.wiv1'), g, h, e)),
|
|
side('int_video/sha256.json', 'video_nk').clip_sha256],
|
|
['tiny (WIH1)', () => hex(sha(wai.decodeWih1(rd('int_hyper/tiny_bitstream.wih1'), g, h, e).rgb)),
|
|
side('int_hyper/sha256.json', 'tiny').sha256],
|
|
['tiny again, its set held', () => {
|
|
const before = wai.loadedSetStats();
|
|
const got = hex(sha(wai.decodeWih1(rd('int_hyper/tiny_bitstream.wih1'), g, h, e).rgb));
|
|
const after = wai.loadedSetStats();
|
|
if (after.hits !== before.hits + 1 || after.misses !== before.misses) {
|
|
return `hits ${before.hits} -> ${after.hits}, misses ${before.misses} -> ${after.misses}`;
|
|
}
|
|
return got;
|
|
}, side('int_hyper/sha256.json', 'tiny').sha256],
|
|
['tiny with a budget of 0', () => {
|
|
const budget = wai.loadedSetStats().budget;
|
|
wai.setLoadedSetBudget(0);
|
|
const before = wai.loadedSetStats();
|
|
const got = hex(sha(wai.decodeWih1(rd('int_hyper/tiny_bitstream.wih1'), g, h, e).rgb));
|
|
const after = wai.loadedSetStats();
|
|
wai.setLoadedSetBudget(budget);
|
|
if (before.entries !== 0 || after.entries !== 0 || after.hits !== before.hits || after.misses !== before.misses) {
|
|
return `entries ${before.entries} -> ${after.entries}, a lookup was made`;
|
|
}
|
|
return got;
|
|
}, side('int_hyper/sha256.json', 'tiny').sha256],
|
|
['audio (WIA1)', () => {
|
|
const a = wai.decodeWia1(rd('int_audio/audio_bitstream.wia1'), rd('int_audio/audio_model.bin'));
|
|
if (a.sampleRate !== 16000) return `sample rate ${a.sampleRate}`;
|
|
const pcm = a.pcm;
|
|
return hex(sha(Buffer.from(pcm.buffer, pcm.byteOffset, pcm.byteLength)));
|
|
}, side('int_audio/sha256.json', 'audio').sha256],
|
|
['splat (WIS1)', () => {
|
|
const s = wai.decodeWis1(rd('int_splat/splat.wis1'), rd('int_splat/splat_gs.bin'));
|
|
if (s.ranges.length !== 2 * s.nAttr || s.nGaussians !== 1024) return 'ranges or Gaussian count';
|
|
return hex(sha(s.attr));
|
|
}, side('int_splat/sha256.json', 'splat').sha256],
|
|
['a JSON form is refused', () => {
|
|
try { wai.decodeWiv1('wai.video.int_motion', rd('int_video/int_video_bitstream.json')); return 'decoded'; }
|
|
catch (err) { return String(err.message ?? err).split(':')[0]; }
|
|
}, 'bad_magic'],
|
|
];
|
|
|
|
let pass = true;
|
|
for (const [name, run, want] of checks) {
|
|
const got = run();
|
|
const ok = got === want;
|
|
pass = pass && ok;
|
|
console.log(`${ok ? 'PASS' : 'FAIL'} ${name.padEnd(30)} ${String(got).slice(0, 16)}${ok ? '' : ` want ${want}`}`);
|
|
}
|
|
process.exit(pass ? 0 : 1);
|