Files
scriptc/tests/harness/large-artifacts.ts
T
Malte Ubl 20b7da29e7 Stream serialized IR and LLVM instead of building one string
The self-hosted compiler's compact IR is ~515-528M characters, a few
million below V8's maximum string length (2^29 - 24). serializeModule
joined it into one string, deserializeModule parsed one string, and the
native bootstrap comparison read cli.ir.json and cli.ll as utf8 strings,
so any compiler growth crashed --emit=ir and the bootstrap tests with
"RangeError: Invalid string length".

- serializeModuleParts returns the compact encoding as ordered parts
  (header, one part per function, remainder). serializeModule(mod, true)
  is their join, so the on-disk format is byte-identical.
- deserializeModuleBytes splits a UTF-8 Buffer at the root object's
  "functions" array with a byte-level scanner and parses the header and
  each function separately. Malformed input falls back to the
  whole-document parse for its exact error.
- The Node API writes --emit=ir, --emit-ir sidecars and --emit=llvm
  through writeTextParts (the former writeLlvmSource batching writer).
- The native bootstrap harness decodes IR per function and compares
  LLVM as Buffers, removing ABI-check lines at the byte level.
- The self-hosting serialization fixture gains a "bytes" mode so the
  scriptc-compiled serializer exercises both new entry points.

Self-hosted compiler IR (514.6 MB, macOS arm64, Node 24.12):
  read  old 5.85 s / 4.26 GB max RSS -> new 5.84 s / 1.36 GB
  write old 2.05-2.53 s / 4.48 GB    -> new 2.30-2.37 s / 2.10 GB
  2x module (1.03 GB): old serialize and read throw; new round-trips.
The remaining per-string bound is the largest function (16.3M chars,
~33x headroom) and the header (2.3M chars).
2026-10-10 20:39:35 -07:00

32 lines
1.3 KiB
TypeScript

/** Byte-level helpers for compiler-scale text artifacts (the self-hosted
* compiler's LLVM and IR). They avoid decoding a complete artifact into one
* JavaScript string, which V8 caps at 2^29 - 24 UTF-16 units. */
/** Offsets of every occurrence of `needle`. */
export function occurrences(bytes: Buffer, needle: string): number[] {
const found: number[] = [];
const width = Buffer.byteLength(needle);
for (let at = bytes.indexOf(needle); at >= 0; at = bytes.indexOf(needle, at + width))
found.push(at);
return found;
}
/** `bytes` without `length` bytes at `start`. */
export function withoutRange(bytes: Buffer, start: number, length: number): Buffer {
return Buffer.concat([bytes.subarray(0, start), bytes.subarray(start + length)]);
}
/** Remove the first complete line that starts with `prefix` and, including
* its newline, matches `line` (anchored), like `text.replace(/^...\n/m, "")`. */
export function withoutFirstLine(bytes: Buffer, prefix: string, line: RegExp): Buffer {
for (let from = 0; ;) {
const at = bytes.indexOf(prefix, from);
if (at < 0) return bytes;
const newline = bytes.indexOf(10, at);
const stop = newline < 0 ? bytes.length : newline + 1;
if ((at === 0 || bytes[at - 1] === 10) && line.test(bytes.toString("utf8", at, stop)))
return withoutRange(bytes, at, stop - at);
from = at + 1;
}
}