Spaces:
Sleeping
Sleeping
| ; | |
| /** | |
| * GEAR hash WASM - Runtime bytecode generation | |
| * | |
| * Generates a tiny WebAssembly module with a single `nextMatch` function | |
| * that performs the gear hash rolling scan using native i64 arithmetic. | |
| * | |
| * Memory layout (all little-endian): | |
| * 0-2047: Gear lookup table (256 Γ 8 bytes) | |
| * 2048-2055: Hash state (u64, persists across calls) | |
| * 2056-2063: Mask (u64, set per-hasher before each call) | |
| * 4096+: Input buffer | |
| */ | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.MAX_INPUT_SIZE = exports.INPUT_OFFSET = exports.MASK_OFFSET = exports.HASH_OFFSET = exports.TABLE_OFFSET = void 0; | |
| exports.initWasm = initWasm; | |
| exports.wasmNextMatch = wasmNextMatch; | |
| exports.getView = getView; | |
| const table_js_1 = require("./table.js"); | |
| exports.TABLE_OFFSET = 0; | |
| exports.HASH_OFFSET = 2048; | |
| exports.MASK_OFFSET = 2056; | |
| exports.INPUT_OFFSET = 4096; | |
| const PAGES = 8; // 512 KB | |
| exports.MAX_INPUT_SIZE = PAGES * 65536 - exports.INPUT_OFFSET; | |
| let wasmMemory = null; | |
| let wasmView = null; | |
| let wasmFn = null; | |
| function toSignedLeb128(n) { | |
| const bytes = []; | |
| let value = n | 0; | |
| for (;;) { | |
| const byte = value & 0x7f; | |
| value >>= 7; | |
| if ((value === 0 && (byte & 0x40) === 0) || (value === -1 && (byte & 0x40) !== 0)) { | |
| bytes.push(byte); | |
| return bytes; | |
| } | |
| bytes.push(byte | 0x80); | |
| } | |
| } | |
| function toLebU32Padded5(n) { | |
| return [ | |
| (n & 0x7f) | 0x80, | |
| ((n >>> 7) & 0x7f) | 0x80, | |
| ((n >>> 14) & 0x7f) | 0x80, | |
| ((n >>> 21) & 0x7f) | 0x80, | |
| (n >>> 28) & 0x0f, | |
| ]; | |
| } | |
| /** | |
| * Generate the WASM module bytecode. | |
| * | |
| * Exports one function: | |
| * nextMatch(inputStart: i32, inputLen: i32) -> i32 | |
| * | |
| * Reads hash/mask from fixed memory offsets, scans from `inputStart` | |
| * for `inputLen` bytes, writes updated hash back. | |
| * Returns 1-based match position within the scanned range, or -1. | |
| */ | |
| function generateWasmBytes() { | |
| const code = []; | |
| function emit(...bytes) { | |
| code.push(...bytes); | |
| } | |
| // ββ Module header ββ | |
| emit(0x00, 0x61, 0x73, 0x6d); // magic | |
| emit(0x01, 0x00, 0x00, 0x00); // version 1 | |
| // ββ Type section: (i32, i32) -> (i32) ββ | |
| emit(0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f); | |
| // ββ Import section: memory "js"."mem" min=PAGES ββ | |
| emit(0x02, 0x0b, 0x01, 0x02, 0x6a, 0x73, 0x03, 0x6d, 0x65, 0x6d, 0x02, 0x00, PAGES); | |
| // ββ Function section: 1 function, type 0 ββ | |
| emit(0x03, 0x02, 0x01, 0x00); | |
| // ββ Export section: "nextMatch" -> func 0 ββ | |
| emit(0x07, 0x0d, 0x01, 0x09, 0x6e, 0x65, 0x78, 0x74, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00); | |
| // ββ Code section ββ | |
| emit(0x0a); | |
| const sectionSizeOff = code.length; | |
| emit(0x00, 0x00, 0x00, 0x00, 0x00); | |
| emit(0x01); // 1 function body | |
| const funcSizeOff = code.length; | |
| emit(0x00, 0x00, 0x00, 0x00, 0x00); | |
| const bodyStart = code.length; | |
| // Locals: $0 = inputStart (param), $1 = inputLen (param) | |
| // $2 = hash (i64), $3 = mask (i64) | |
| // $4 = ptr (i32), $5 = end (i32) | |
| emit(0x02, 0x02, 0x7e, 0x02, 0x7f); | |
| // Load hash from memory[HASH_OFFSET] | |
| emit(0x41, ...toSignedLeb128(exports.HASH_OFFSET)); | |
| emit(0x29, 0x03, 0x00); | |
| emit(0x21, 0x02); | |
| // Load mask from memory[MASK_OFFSET] | |
| emit(0x41, ...toSignedLeb128(exports.MASK_OFFSET)); | |
| emit(0x29, 0x03, 0x00); | |
| emit(0x21, 0x03); | |
| // ptr = inputStart | |
| emit(0x20, 0x00); | |
| emit(0x21, 0x04); | |
| // end = inputStart + inputLen | |
| emit(0x20, 0x00); | |
| emit(0x20, 0x01); | |
| emit(0x6a); | |
| emit(0x21, 0x05); | |
| // block $done | |
| emit(0x02, 0x40); | |
| // loop $loop | |
| emit(0x03, 0x40); | |
| // if ptr >= end β break | |
| emit(0x20, 0x04); | |
| emit(0x20, 0x05); | |
| emit(0x4e); | |
| emit(0x0d, 0x01); | |
| // hash = (hash << 1) + table[mem[ptr] * 8] | |
| emit(0x20, 0x02); | |
| emit(0x42, 0x01); | |
| emit(0x86); | |
| emit(0x20, 0x04); | |
| emit(0x2d, 0x00, 0x00); | |
| emit(0x41, 0x03); | |
| emit(0x74); | |
| emit(0x29, 0x03, 0x00); | |
| emit(0x7c); | |
| emit(0x22, 0x02); | |
| // if (hash & mask) == 0 β match | |
| emit(0x20, 0x03); | |
| emit(0x83); | |
| emit(0x50); | |
| emit(0x04, 0x40); | |
| // Store updated hash | |
| emit(0x41, ...toSignedLeb128(exports.HASH_OFFSET)); | |
| emit(0x20, 0x02); | |
| emit(0x37, 0x03, 0x00); | |
| // Return: ptr - inputStart + 1 | |
| emit(0x20, 0x04); | |
| emit(0x20, 0x00); | |
| emit(0x6b); | |
| emit(0x41, 0x01); | |
| emit(0x6a); | |
| emit(0x0f); | |
| emit(0x0b); // end if | |
| // ptr++ | |
| emit(0x20, 0x04); | |
| emit(0x41, 0x01); | |
| emit(0x6a); | |
| emit(0x21, 0x04); | |
| emit(0x0c, 0x00); // br $loop | |
| emit(0x0b); // end loop | |
| emit(0x0b); // end block | |
| // no match: store hash, return -1 | |
| emit(0x41, ...toSignedLeb128(exports.HASH_OFFSET)); | |
| emit(0x20, 0x02); | |
| emit(0x37, 0x03, 0x00); | |
| emit(0x41, 0x7f); | |
| emit(0x0b); // end function | |
| // Backpatch sizes | |
| const bodySize = code.length - bodyStart; | |
| const bsPatch = toLebU32Padded5(bodySize); | |
| for (let i = 0; i < 5; i++) | |
| code[funcSizeOff + i] = bsPatch[i]; | |
| const secSize = code.length - sectionSizeOff - 5; | |
| const ssPatch = toLebU32Padded5(secSize); | |
| for (let i = 0; i < 5; i++) | |
| code[sectionSizeOff + i] = ssPatch[i]; | |
| return new Uint8Array(code); | |
| } | |
| function initWasm() { | |
| if (wasmFn) | |
| return; | |
| const bytes = generateWasmBytes(); | |
| wasmMemory = new WebAssembly.Memory({ initial: PAGES }); | |
| const module = new WebAssembly.Module(bytes); | |
| const instance = new WebAssembly.Instance(module, { js: { mem: wasmMemory } }); | |
| wasmFn = instance.exports.nextMatch; | |
| wasmView = new Uint8Array(wasmMemory.buffer); | |
| const dv = new DataView(wasmMemory.buffer); | |
| for (let i = 0; i < 256; i++) { | |
| dv.setBigUint64(exports.TABLE_OFFSET + i * 8, table_js_1.GEAR_TABLE[i], true); | |
| } | |
| } | |
| function wasmNextMatch(inputStart, inputLen) { | |
| return wasmFn(inputStart, inputLen); | |
| } | |
| function getView() { | |
| return wasmView; | |
| } | |