codegen: inline plain-array pop tier with js_array_pop_f64 as the fallback - #8944
Conversation
…lback A fresh symbol-level profile of the wolf-ecs entity cycle (10 s twin) puts `js_array_pop_f64` at 10.1% of self time (8.1% on add/remove) although every one of those pops — `SparseSet.remove`'s and `createEntity`'s `packed.pop()` — takes its plain-array fast path: the cost is the call, the heap-address classification and the flag resolution around one length store. `expr/array_pop.rs` emits that decision inline at both `pop` routes (the erased class-field `NativeMethodCall` and the claimed-array `lower_array_method`), mirroring the runtime's admission exactly: a POINTER-tagged heap handle, `GC_TYPE_ARRAY`, not forwarded, none of FROZEN|SEALED|NO_EXTEND|ARRAY_DESCRIPTORS (0x407), the `PERRY_ARRAY_INDEX_FAST_PATH_INVALIDATED` latch clear, `0 < length <= capacity <= 1e8`, and no hole in the popped slot. The inline arm reads the element and stores the decremented `i32` length — no pointer store, no allocation, no GC bookkeeping, exactly like the runtime's fast path. Everything else, including the empty array, still calls `js_array_pop_f64`. IR census test covers both routes: the header gate's type/mask/latch tests, the hole check, a call-free inline arm, and the retained runtime fallback. Claude-Session: https://claude.ai/code/session_019WVcWKmYsUBnnFB7nBgbBJ
|
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 (5)
Included review availability: Your plan provides up to 8 included reviews per hour; 0 remain after this review. 📝 WalkthroughWalkthrough
ChangesInline Array Pop
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: 🔵 Low · up to The optimization adds a direct array read and length write for eligible receivers; if a non-array heap address is misclassified, it could read or modify invalid object layout. The existing fallback limits impact, but the admission logic should be aligned with the canonical classifier or the exclusion explicitly validated before merge. Sequence Diagram(s)sequenceDiagram
participant ArrayPopCaller
participant lower_array_pop_inline
participant GCHeap
participant js_array_pop_f64
ArrayPopCaller->>lower_array_pop_inline: pass boxed array receiver
lower_array_pop_inline->>GCHeap: validate array metadata and read final element
alt inline checks pass
lower_array_pop_inline->>GCHeap: decrement and store length
lower_array_pop_inline-->>ArrayPopCaller: return boxed popped element
else fallback condition
lower_array_pop_inline->>js_array_pop_f64: execute runtime pop
js_array_pop_f64-->>ArrayPopCaller: return runtime result
end
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Description checkExplanation The description explains the implementation, motivation, scope, and verification status, but it does not follow the repository template. It omits the required Summary, Changes, Related issue, Test plan, Screenshots / output, and Checklist sections, and it reports key verification work as still in progress. Resolution Rewrite the description using the repository template. Add the required section headings, list concrete changes, state the related issue or use "n/a", provide completed test commands and results, complete the checklist, and include output if applicable. Full details: Docstring CoverageExplanation Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 4 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches🧪 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 |
|
Merged. The inline admission mirrors the runtime fast path's decision — POINTER-tagged heap handle, Validation: codegen 1334/0, runtime 2771/0 ( |
|
Gates on Semantics probe (holes, frozen, non-writable Mac-mini paired screens (11 pairs), main
That is far less than 8–10% of pop self time should buy, and add/remove moves the wrong way, so I'm not calling this a retain yet — re-profiling the PR build on Linux to see whether the inline arm is actually being taken on the hot pops (if |
|
Resolved why the ECS numbers are small: wolf-ecs's Where the tier does apply it pays: plain-array push/pop microbenchmark (a class field Recommendation: merge as a general improvement (semantics pinned by the e2e test, |
What
arr.pop()on an Array-admitted receiver — an erased class field (this.packed.pop(), theNativeMethodCall { array, pop }route) or a claimed-array local/parameter (lower_array_method) — now emits an inline plain-array tier (expr/array_pop.rs) withjs_array_pop_f64kept as the fallback. The inline admission mirrors the runtime fast path's decision exactly:GC_TYPE_ARRAY, notGC_FLAG_FORWARDED, none ofFROZEN|SEALED|NO_EXTEND|ARRAY_DESCRIPTORS(0x407) in_reserved(frozen / non-writable-length arrays must throw, descriptor-bearing ones need the descriptor-aware[[Delete]]);PERRY_ARRAY_INDEX_FAST_PATH_INVALIDATED == 0(pollutedArray.prototype);0 < length <= capacity <= 1e8and the popped slot is not the hole sentinel.The inline arm reads the element and stores the decremented
i32length — no pointer store, no allocation, no GC bookkeeping, which is also all the runtime's fast path does. The empty array still calls (that arm is #8934's runtime fast path to keep). No runtime changes.Why
A fresh symbol-level profile of the wolf-ecs twins (Linux
perf, 10 s):js_array_pop_f64is 10.1% of self time in the entity cycle and 8.1% in add/remove, while every pop there (SparseSet.remove's andcreateEntity'spacked.pop()) takes the runtime's plain-array fast path — the cost is the call, the heap-address classification (try_read_gc_header) and the flag resolution around a single length store.Verification (local, per the campaign rule; no CI wait)
array_pop_takes_the_inline_tier_with_the_runtime_call_as_fallbackfor both routes:apop.hdrtestsGC_TYPE_ARRAY, the1031mask and the latch;apop.readtests the hole sentinel;apop.takeis onestore i32and no call;apop.slowcallsjs_array_pop_f64.-D warnings, file size, GC store inventory, raw-handle debt, census, local-binding, addr-class) and an e2e semantics test (holes, frozen, non-writable length, Array subclass, string receiver, accessor slot, polluted prototype — node-identical output) are in progress; Mac-mini paired screens main → PR (2 s + 50 ms, 11 pairs) follow. I'll post all three as comments.https://claude.ai/code/session_019WVcWKmYsUBnnFB7nBgbBJ
Summary by CodeRabbit
Performance
Array.prototype.pop()performance for eligible plain arrays by handling common cases inline.Bug Fixes
undefinedcorrectly.