kenqtade's picture
Use ken-q org namespace for project Spaces
0865492
Raw
History Blame Contribute Delete
6.01 kB
"use strict";
/**
* 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;
}