perf(codegen/runtime): #6812 — spill lanes make append-past-capacity arrays clone-eligible (w13 beats node)#6856
Conversation
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
|
Follow-up evidence on the final tip:
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughObject-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. ChangesSpill lane clone 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
Possibly related issues
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
changelog.d/6812-spill-lanes.mdcrates/perry-codegen/src/stmt/loops.rscrates/perry-runtime/src/object/mod.rscrates/perry-runtime/src/proxy/put_value.rsdocs/object-write-matrix.md
…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
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] = vwhere7was 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):max(field_count, floor)) or spill (slot past it but covered by the object-ownedmeta.spillbuffer — validated as a live, non-forwarded plain array whose length high-water exceeds the slot, mirroring the shared-keys validation).+1packing cannot carry into it (find_slotcaps slots at 4096).Emitter (
lower_object_array_write_versioned_forfast nest):obj → meta(wordheader-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).offset_of!(ObjectMeta, spill) == 32,size_of::<ArrayHeader>() == 8) lock the codegen offsets.doneblocks, so the inner back-edge gained a dedicated latch block and the counter phi names its true predecessor.Validation
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=0where spill lanes correctly self-reject since legacy objects have no buffers;PERRY_GEN_GC=0).Refs #6812.
https://claude.ai/code/session_01QJ5mwMDPc63tNLAFPdthAG
Summary by CodeRabbit
Performance
Bug Fixes
Documentation