Skip to content

Order the indexed inline cache by observed receiver kind: a lazy array pays ~49% of its read in guards that cannot match #10118

Description

@proggeramlug

The problem

Every indexed read on an unknown receiver walks a fixed chain of guards in emitted order, and a receiver only reaches the tier that can serve it after every earlier tier has failed. For a JSON.parse array — the common case in any JSON-shaped workload — that means paying for two whole families of guards that can never match.

Static breakdown of the rows[7].id loop in the JSON access benchmark (688 IR instructions, from --trace llvm on the post-#10114 compiler):

block family IR instrs share
array-index IC tiers (ordinary / subclass / shape / family / spill) 212 30.8%
property IC (.id) 183 26.6%
typed-array probe (tav.*) 123 17.9%
loop / arithmetic 104 15.1%
string equality 30 4.4%
GC poll + root barrier 29 4.2%
lazy probe (the tier that actually serves the read) 7 1.0%

The tier that serves the read is 1% of the footprint. The typed-array probe and the array-index tiers — ~49% — exist to be missed by this receiver, every iteration, forever.

Measured cost after #10114: records_array_16k:repeat is 116.7 retired instructions per rows[7].id, of which ~25 are inside the probe and ~92 are the emitted guard chain. Node does the whole read in roughly 8 cycles. The remaining access rows sit at 1.47×–4.58× the better of Node/Bun.

Proposal: order the chain by observed receiver kind

The IC site already carries a per-site cache slot (#9708 made them lazily allocated, 8 bytes, primed on first use). It records a shape identity today; it does not record what kind of receiver was seen.

Record the observed GcHeader::obj_type on prime, and branch on it first:

  • GC_TYPE_LAZY_ARRAY → straight to js_lazy_array_index_probe
  • GC_TYPE_ARRAY → straight to arrlike.ic.array_guard
  • typed array → straight to the tav.* tier
  • anything else, or a kind change → the existing full chain, unchanged

This is not a new proof — every guard that currently runs still runs on the path that needs it. It only stops a receiver from paying for the tiers that provably cannot match it. A polymorphic site that sees several kinds degrades to today's behaviour.

Why this is the right next lever

  • It is the dominant remaining cost on the worst rows in the JSON matrix, and those rows are the last family still behind Node and Bun.
  • It is receiver-kind-generic: typed arrays and ordinary Arrays behind an erased receiver benefit for the same reason, not just JSON.
  • It composes with, rather than duplicates, Unify lazy JSON arrays into GC_TYPE_ARRAY: laziness as element state, not an object type #10098 — that issue removes GC_TYPE_LAZY_ARRAY as a distinct kind entirely, at which point this dispatch has one fewer arm but the same shape.

Validation it needs

  • The emitted chain must be provably unchanged for a receiver whose recorded kind is absent or stale — an IR claim test per tier.
  • Kind-transition coverage: a site that sees a lazy array, then an ordinary Array, then a typed array, then a Proxy, must return what the full chain returns at every step.
  • The existing 234-row lazy-array matrix (native/shadow roots × auto/tape/direct parsers × normal/scheduled/full-GC) must stay byte-identical.
  • Code size is the trap: perf(codegen): serve lazy JSON array reads from the indexed inline cache #10114 measured that inlining a tier at every read site cost unrelated rows up to 5% through layout alone, and that the same cost relocates between rows when unrelated changes land. Whatever this emits must be measured for emitted-size growth, not only for speed.

Refs #10114 (added the lazy tier this would reorder), #10098, #793.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew capability or improvement

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions