Bytecode cache: numbered strings with stored hashes, so repeat lookups skip the atom table

Merged
JA
3mo ago
+186-19

Atomizing identifiers and string constants was ~23% of decoding a code block (hash, probe, compare on every reference; the table regrowing as they trickle in). Now:

  • Each distinct non-symbol string record stores its StringImpl::hash() and a sequence number assigned in encode order. Decode adds via AtomStringImpl::add(HashTranslatorCharBuffer{chars, hash}) (no rehash) and remembers the atom per number in a Vector on the Decoder; later references are an index. 1- and 2-character strings that live inline in their slot (#499) are memoized in two flat tables keyed by their characters.
  • String constants (CachedJSValue) decode as plain StringImpls (aliasing the payload when long) — only identifiers need to be atoms — so half the string references never touch the table.
  • HashTable::reserveCapacity / AtomStringImpl::reserveCapacityForCurrentThread: a block reserves for its identifiers+constants before adding them.
  • Update payloads appended by CachedBytecode::addFunctionUpdate (jsc shell) continue the base payload's numbering: Encoder takes a first number, CachedBytecode records the end, encodeFunctionCodeBlock takes it.

30 MB / 58-library bundle, jsc shell, interleaved runs on pinned cores (box under load, so medians):

main (aff53044)this
decode every top-level function (60k), cycles:u1170 M874 M (−25%)
decode everything (128k blocks), cycles:u2164 M1781 M (−18%)
cache size87.7 MB89.2 MB (+8 B per string record)

JSTests/stress through the disk cache (persistent + non-persistent): unchanged vs main.

For scale: on the same bundle compiled with bun build --compile --bytecode, samply puts bytecode decode at ~21% of main-thread startup (150 ms of 0.72 s), and the atom table is over a third of that.