Skip to content

perf(codegen/runtime): #6812 — spill lanes make append-past-capacity arrays clone-eligible (w13 beats node)#6856

Merged
proggeramlug merged 3 commits into
mainfrom
perf/6812-spill-lanes
Jul 26, 2026
Merged

perf(codegen/runtime): #6812 — spill lanes make append-past-capacity arrays clone-eligible (w13 beats node)#6856
proggeramlug merged 3 commits into
mainfrom
perf/6812-spill-lanes

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Summary

The final piece of the w13 ladder: the whole-loop write clone learns spill lanes, so arrays whose written property lives past the inline slot capacity — the append-then-update pattern (objs[i][7] = v where 7 was added to 5-field literals) — run in the call-free clone instead of the generic per-write path.

Canonical w13: ~155 ms → 7 ms (node ~13 ms) — beats node. Guard trace shows zero rejections; checksums identical.

Guard (object_array_numeric_write_slots):

  • Each lane is classified from the FIRST receiver: inline (slot within max(field_count, floor)) or spill (slot past it but covered by the object-owned meta.spill buffer — validated as a live, non-forwarded plain array whose length high-water exceeds the slot, mirroring the shared-keys validation).
  • Every later receiver must sit on the SAME side of its own alloc_limit and pass the same coverage proof — a wider receiver holding the slot inline while the lane is spill would make the emitter write the wrong memory, so mode mismatch rejects into the fallback (covered by sanity s3: the learned-resize builder array where instance Support custom menu bar items #1 spills and later instances are inline).
  • Typed-layout-intact receivers reject spill lanes conservatively.
  • Encoding: bit 15 of the u16 result lane; the caller's +1 packing cannot carry into it (find_slot caps slots at 4096).

Emitter (lower_object_array_write_versioned_for fast nest):

  • Lane decode masks the flag; each store site branches on the loop-invariant flag (LLVM unswitches it out of the nest). Inline lanes are unchanged; spill lanes store through obj → meta (word header-1, the documented last header field) → spill (ObjectMeta word 4) → elements (one word past the 8-byte ArrayHeader) — two dependent loads, still call-free, preserving the guard's no-GC interval. Numeric stores create no references, so no barriers or layout notes (the same audit argument as the inline lanes).
  • Const assertions beside the runtime structs (offset_of!(ObjectMeta, spill) == 32, size_of::<ArrayHeader>() == 8) lock the codegen offsets.
  • The per-lane store chain ends in done blocks, so the inner back-edge gained a dedicated latch block and the counter phi names its true predecessor.

Validation

  • New spill_lanes_sanity.ts (5 cases): canonical append-past-capacity, two spill lanes + one inline lane in a single nest, the mode-mismatch fallback (learned-resize builders), post-clone readback through get/entries/values/JSON, dynamic inner bound + spill lane — byte-identical vs node v26.3.0 in all four runtime modes (default; PERRY_GC_FORCE_EVACUATE=1 PERRY_GC_VERIFY_EVACUATION=1; PERRY_OBJECT_SPILL=0 where spill lanes correctly self-reject since legacy objects have no buffers; PERRY_GEN_GC=0).
  • Existing sanity batteries (intkey_temps, empty_site, builder_fold, mul_index) byte-identical.
  • perry-hir 212 / perry-codegen 253 / perry-runtime 1465 — green (serial protocol).
  • Gap gate results in follow-up comment.
  • Timing caveat: the 7 ms measurement was taken at ambient load ~8 (a concurrent session was building); an idle 15-run protocol will be posted as follow-up evidence if numbers move.

Refs #6812.

https://claude.ai/code/session_01QJ5mwMDPc63tNLAFPdthAG

Summary by CodeRabbit

  • Performance

    • Improved object-array cloning speed when writes go beyond inline field capacity.
    • Added more efficient “spill lane” handling, improving throughput for append-past-capacity workloads.
  • Bug Fixes

    • Fixed multi-receiver write preflight/routing so each lane is consistently treated as inline vs overflow, with spill coverage validated per receiver.
  • Documentation

    • Updated the object-write performance matrix entry to reflect broader clone eligibility and updated benchmark outcomes.

Ralph Küpper added 2 commits July 26, 2026 17:31
The guard classifies each lane as INLINE or SPILL from the first receiver
(spill = slot past the inline alloc_limit but covered by the object-owned
meta.spill buffer: live non-forwarded array, length high-water > slot);
every later receiver must sit on the SAME side of its own alloc_limit and
pass the same coverage proof, so the emitter's per-lane store sequence is
uniform across the proven prefix. Spill lanes set bit 15 of their u16
result lane — the +1 packing cannot carry into it (find_slot caps slots
at 4096). Typed-layout-intact receivers reject spill lanes conservatively.

The fast nest branches per lane on the loop-invariant flag (LLVM
unswitches it): inline lanes store as before; spill lanes store through
obj → meta(word header-1) → spill(word 4) → elements(word 1+slot). Both
paths stay call-free raw numeric stores, preserving the guard's no-GC
interval; const assertions beside the runtime structs lock the codegen
offsets. The store chain ends in per-lane done blocks, so the inner
back-edge gained a dedicated latch and the counter phi names its true
predecessor.

This makes append-past-capacity arrays (canonical w13: a 6th key on
5-field literals) clone-eligible after the #6841 peel primes their shapes.

Claude-Session: https://claude.ai/code/session_01QJ5mwMDPc63tNLAFPdthAG
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Follow-up evidence on the final tip:

  • 18-cell matrix (3 alternated runs, ambient load ~8, checksums identical): w13 12 → 7 ms = 0.58, beats node; w16 holds (0.29 with the peel round); no other row moved. 12 of 18 rows now beat node.
  • Gap gate: the 6 triaged parity + 6 triaged cold-cache compile entries behaved as recorded; the single new flag, test_gap_webcrypto_async_threadpool, is the documented flaky threadpool-oracle family — it byte-matches node in 3 consecutive isolated runs (the gate run happened at load ~22 while a concurrent session was building).
  • Suites (serial): perry-hir 212, perry-codegen 253, perry-runtime 1465 — 0 failures.
  • Sanities: new spill_lanes_sanity.ts (5 cases incl. the mode-mismatch fallback) + all four existing batteries byte-identical vs node in all four runtime modes.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 84ffe93c-cf34-403d-9477-9515cc2048e8

📥 Commits

Reviewing files that changed from the base of the PR and between 9999ca5 and 52be25b.

📒 Files selected for processing (2)
  • changelog.d/6812-spill-lanes.md
  • crates/perry-codegen/src/stmt/loops.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/perry-codegen/src/stmt/loops.rs

📝 Walkthrough

Walkthrough

Object-array numeric write cloning now classifies lanes as inline or spill, validates spill coverage across receivers, encodes lane modes, and emits branch-local stores to object fields or object-owned spill buffers. Runtime layout assertions and documentation describe the updated path.

Changes

Spill lane clone path

Layer / File(s) Summary
Classify and encode spill lanes
crates/perry-runtime/src/proxy/put_value.rs
Preflight validates spill-buffer coverage, enforces consistent lane modes across receivers, and encodes spill lanes in packed slot values.
Emit inline and spill stores
crates/perry-codegen/src/stmt/loops.rs
The fast loop decodes lane flags, routes each lane to inline or spill storage, converges through per-lane blocks, and uses a dedicated inner latch.
Lock layout assumptions and describe eligibility
crates/perry-runtime/src/object/mod.rs, changelog.d/6812-spill-lanes.md, docs/object-write-matrix.md
Runtime assertions enforce spill-layout offsets, and documentation records the updated clone behavior and eligibility path.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant object_array_numeric_write_slots
  participant lower_object_array_write_versioned_for
  participant ObjectMeta_spill_buffer
  object_array_numeric_write_slots->>object_array_numeric_write_slots: classify and encode spill lanes
  object_array_numeric_write_slots->>lower_object_array_write_versioned_for: pass packed lane values
  lower_object_array_write_versioned_for->>ObjectMeta_spill_buffer: store spill lanes through meta
  lower_object_array_write_versioned_for-->>lower_object_array_write_versioned_for: continue through inner latch
Loading

Possibly related issues

Possibly related PRs

  • PerryTS/perry#6821: Earlier generalized object-array numeric write guard work in the same runtime and codegen paths.
  • PerryTS/perry#6822: Related guard and fast-path changes for multi-key slot validation.
  • PerryTS/perry#6849: Introduced the object-owned spill buffer consumed by this codegen path.

Suggested labels: performance

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the spill-lane clone-eligibility and runtime/codegen performance change.
Description check ✅ Passed The description covers the PR's summary, concrete changes, related issue, and validation evidence, even if it doesn't use the exact template headings.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/6812-spill-lanes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@changelog.d/6812-spill-lanes.md`:
- Line 1: Align the w13 benchmark baseline across
changelog.d/6812-spill-lanes.md at line 1 and docs/object-write-matrix.md at
line 50 by using one authoritative timing, or explicitly document the distinct
measurement protocols and commits for the approximately 155 ms and 160 ms
results; preserve the reported optimized timing and surrounding benchmark
context.

In `@crates/perry-codegen/src/stmt/loops.rs`:
- Around line 2650-2654: Update the meta_word calculation in the loop emitter to
use the ILP32 ObjectHeader byte layout: derive the meta field’s byte offset as
header size minus the 4-byte field width, matching the meta_ptr_size computation
in new.rs, rather than converting the header size to 8-byte words. Preserve
header_words for its existing uses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 53671d6a-a8bc-40b4-9512-7d4b8ac07c62

📥 Commits

Reviewing files that changed from the base of the PR and between 6899e75 and 9999ca5.

📒 Files selected for processing (5)
  • changelog.d/6812-spill-lanes.md
  • crates/perry-codegen/src/stmt/loops.rs
  • crates/perry-runtime/src/object/mod.rs
  • crates/perry-runtime/src/proxy/put_value.rs
  • docs/object-write-matrix.md

Comment thread changelog.d/6812-spill-lanes.md Outdated
Comment thread crates/perry-codegen/src/stmt/loops.rs Outdated
…eline

The spill store path addressed 'meta' as 8-byte word (header_words - 1),
which on ILP32 (arm64_32: 24-byte header, 4-byte meta at offset 20) reads
the wrong pointer. Address it by byte offset with a pointer-width load
(zext to i64 on ILP32), mirroring the new.rs allocator's meta store.
Changelog now cites the same w13 baseline lineage as the matrix doc.

Claude-Session: https://claude.ai/code/session_01QJ5mwMDPc63tNLAFPdthAG
@proggeramlug
proggeramlug merged commit 563c359 into main Jul 26, 2026
29 of 30 checks passed
@proggeramlug
proggeramlug deleted the perf/6812-spill-lanes branch July 26, 2026 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant