perf(class-fields): a conforming pointer store skips the layout note (#5094) - #7698
Conversation
|
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 ignored due to path filters (1)
📒 Files selected for processing (10)
📝 WalkthroughWalkthroughThe code generator now identifies pointer-declared class slots and conditionally elides redundant layout-note calls after checking object header state. Tests validate conforming and fallback IR paths. The workspace version and changelog were updated. ChangesClass-field layout-note elision
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant ClassFieldStore
participant LayoutHelper
participant WriteBarrier
participant ParentHeader
participant GCLayoutRuntime
ClassFieldStore->>LayoutHelper: check class and field layout
LayoutHelper-->>ClassFieldStore: conforming pointer-slot flag
ClassFieldStore->>WriteBarrier: emit pointer-tested store
WriteBarrier->>ParentHeader: test layout state
ParentHeader-->>WriteBarrier: header state
WriteBarrier->>GCLayoutRuntime: call js_gc_note_slot_layout on fallback
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 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 |
The #5094 layout-note conditional splits the gc_bookkeeping block into three, and LLVM emits gc_bookkeeping.done BETWEEN the entry and layout_note, so the region is not textually contiguous -- neither the old first-block slice nor a wider text slice describes it. Walk the CFG from the guard entry, stopping at the join. Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix
023f0b9 to
e6ddfbf
Compare
Audit — merging as v0.5.1404, after fixing two tests it brokeI could not break the elision. I could break the branch, and the way it broke is the interesting part. Soundness: I tried the five ways this could be wrongThe note is a genuine no-op on the path being skipped —
The thread I pulled hardest: can the scanner trust What it actually broke, and why neither of us saw it
The barrier had not moved. Your new
A wider text slice would not have worked either — LLVM emits Why CI couldn't tell you: per-PR Two follow-ups, neither blocking
Gates 26/26. |
The P3 investigation resolved the ticket's contradiction as "already fixed"
P3 (
gc-handoff/PROMPT-P3-class-canonical-layout.md) opens with a contradictionto adjudicate before writing any fix: profiles showing 25–29% of class-heavy
benches in per-object layout side tables, against a
shape_install_sharedpaththat is supposed to store one descriptor per shape with no per-object insert.
Two hypotheses were named. Both are false, and the ticket's own acceptance
criteria 1 and 2 are already satisfied on
main— its table was measured ata853135aa, before P1 (#7686).Counters from an instrumented runtime,
tree.ts:INIT_KEYS_ZERO = 0; every instance reachesshape_install_sharedkeyed.a.peer = bdemotes the descriptor. False.NOTE_DOWNGRADE = 0.the emptiness flag never arms, and
layout_forget_objecttook its slow pathonce in 64 million calls.
What was actually left, and is fixed here
20,447,154 of those 20,447,156
js_gc_note_slot_layoutcalls returnConforms— a cross-crate call, header decode, TLS touch and
SHAPE_LAYOUTShashmaplookup per pointer store, to re-derive what codegen knew when it emitted the
mask.
class_field_store_needs_layout_note's doc comment named this case, whyit was left alone, and the condition that would unblock it:
#6921 is closed. This takes the elision as a live header test with the real
note kept on the cold arm — see the changelog fragment for the two facts that
make the taken arm a proof (the #5093 precheck has already pinned the receiver's
keys_arrayto this class's keys global; the slot is in the pointer mask andchecked absent from the raw-f64 mask, so neither downgrade arm can fire).
Measured — quiet M1 mini, best-of-3, interleaved, stdout byte-identical
cyclestree_widetreeUnchanged:
churn1.21,push_cls0.89,churn_alloc0.89,retain2.41,retain10.04,retain_wide3.38,retain_wide10.06,deeplist0.03,cls_mistyped0.02. Peak RSS unchanged.The profile states it more plainly than the clock: on
cycles_big,layout_note_slotis the second-heaviest leaf frame in the base arm (155samples) and is absent from the fix arm entirely. Wall-clock share is
smaller because these workloads are GC-dominated under the current default
pacing.
Safety
The intact bit is not made sticky and no downgrade path is touched.
cls_mistyped.ts(anumber-declared field constructed with a heap stringper instance — it must demote or the collector never traces those strings)
still prints
20000 string payload-19999.PERRY_GC_VERIFY_MARK=1 PERRY_GC_VERIFY_EVACUATION=1clean over the bench set.PERRY_GC_TRACEcycle counts and copied bytes identical between arms(
churn13,tree20).cargo test -p perry-codegen --lib781 passed;-p perry-runtime --lib1935passed.
cargo fmt --all -- --checkclean.Three new tests, unit-level so
cargo-testsees them (#5960), each verified ableto fail: the positive asserts the elision is reached (a predicate silently
answering
falseeverywhere would otherwise be invisible), and the negativeasserts a slot in neither mask (
flag: boolean) keeps its unconditional note —sabotaging the predicate to return
truemakes that test fail.Note for reviewers
Measuring P3 at all required #7690. On
mainbefore it merged,treeran204 s against an expected 4.4 s,
churn9.1 s with 1304 GC cycles against105, with
"copying_nursery":{"eligible":false}on every trace line — #7687 hadlanded the immobility guard without the pacing half.
cycleswas nearlyunaffected (0.36 s), which is why that state was easy to miss. All numbers here
are on
mainwith #7690 merged.Summary by CodeRabbit
Performance
Bug Fixes
Documentation
Tests