fix(codegen): read-PIC honors descriptors installed after priming (#6080) - #6254
Conversation
) The generic property-get inline cache hit predicate checked only the receiver's keys_array pointer, GC type, object_type, non-null keys and closure-magic. It did not consult OBJ_FLAG_HAS_DESCRIPTORS, so a call site primed on a plain data property kept serving a raw slot load after Object.defineProperty converted that key to an accessor (or redefined its descriptor) — keys_array is unchanged by defineProperty, so the stale hit path bypassed the getter entirely. Add a descriptor-flag guard to the hit predicate (mirroring the one in class_field_inline_guard.rs): load the GcHeader `_reserved` i16 at offset -6 and force a PIC miss whenever OBJ_FLAG_HAS_DESCRIPTORS (0x800) is set, routing the read through js_object_get_field_ic_miss -> js_object_get_field_by_name, which honors descriptors. The guard is a single load+and+cmp folded into the existing `hit` cond_br; normal descriptor-free objects keep hitting the cache. Scope: this closes sub-bug (b) of #6080 (defineProperty-after-prime bypass), which has a deterministic repro. The separate ABA-staleness sub-bug (a) — the cache slot is a raw, unrooted keys_array address that can be recycled after GC — is left as a follow-up; it needs GC root registration / content validation on the hot path and is not deterministically reproducible. Regression test: test-files/test_gap_6080_defineproperty_after_prime.ts
|
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)
📝 WalkthroughWalkthroughThe generic property GET PIC fast path now rejects receivers marked as having descriptors, routing them through descriptor-aware miss handling. A regression test primes property reads, applies ChangesDescriptor-aware property reads
Estimated code review effort: 3 (Moderate) | ~15–30 minutes Possibly related issues
Possibly related PRs
🚥 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 |
…s recycling (#6080) (#7434) * fix(gc/codegen): epoch-gate read-PIC pointer tokens against GC address recycling (#6080) #6080(a) residual: class instances (and any receiver without a #6804 ShapeId stamp) still prime the RAW keys-array pointer into their per-site @perry_ic_N cache. Those globals are invisible to every GC scanner, and GC_FLAG_SHAPE_SHARED keeps a keys array LIVE but not address-STABLE - the copying minor moves it like anything else and the vacated from-space address is recycled, so a stale prime can pointer-match a different-shape keys array and the inline hit path loads the wrong slot, silently. Fix (the epoch design from the #6080 thread, narrowed to pointer tokens): - runtime: process-global PERRY_IC_EPOCH (starts at 1 so a zeroinitializer cache can never match), bumped in GcStats::record_collection - the single per-collection funnel - and again at budgeted-sweep ENTRY, because budgeted sweep slices interleave with the mutator before the end-of-cycle funnel runs. - js_object_get_field_ic_miss snapshots the live epoch into cache[2] at prime time (cache widens [i64;2] -> [i64;3]; the emitted global was already [8 x i64], so no layout change). - codegen: the inline monomorphic hit predicate requires cache[2] == @PERRY_IC_EPOCH before trusting a pointer token. Shape-ID tokens (bit 62) skip the check - ids are never reused - so the hot stamped-plain-object population never re-primes after GC. Coverage: IR contract test asserts the emitted guard (epoch-slot gep + @PERRY_IC_EPOCH load); runtime tests assert the funnel bumps the epoch and that a pointer-token prime goes stale on bump (the guard's exact inputs, so the instrument provably CAN fail). Probe run on Windows: 200k class-instance reads through one monomorphic site under copying minors (copied_objects>0 confirmed via PERRY_GC_DIAG) and under PERRY_GEN_GC=0, all values correct; defineProperty-after-prime (#6254 half) still honored. Sibling not covered here: the #6812 3-way dynamic-key WRITE IC compares the same discriminated shape token and packs 4 ways into its 8-slot global (no free epoch slot); noted on #6080. * docs: changelog fragment for #7434 --------- Co-authored-by: Ralph Kuepper <ralph@skelpo.com>
Summary
Fixes sub-bug (b) of #6080: the read inline-cache (Read-PIC) bypassed a property/accessor descriptor installed after the call site was primed.
The generic property-get IC hit predicate (
expr/property_get/generic_dispatch.rs) validated the receiver'skeys_arraypointer, GC type,object_type, non-null keys and closure-magic — but never checkedOBJ_FLAG_HAS_DESCRIPTORS. SinceObject.definePropertydoes not changekeys_array, a site primed on a plain data property kept serving a raw slot load after that key became a getter (or was redefined), silently returning the stale value instead of invoking the accessor.Fix
Add a descriptor-flag guard to the hit predicate, mirroring the existing one in
class_field_inline_guard.rs: load the GcHeader_reservedi16 at offset-6and force a PIC miss wheneverOBJ_FLAG_HAS_DESCRIPTORS(0x800) is set. The read then routes throughjs_object_get_field_ic_miss→js_object_get_field_by_name, which honors descriptors. Cost is a single load+and+cmp folded into the existinghitcond_br; descriptor-free objects keep hitting the cache.Verification
42, and data-redefine99).test-files/test_gap_6080_defineproperty_after_prime.ts— byte-for-byte parity withnode --experimental-strip-types.Scope / follow-up
Leaves the separate ABA-staleness sub-bug (a) of #6080 as a follow-up: the cache slot stores a raw, unrooted
keys_arrayaddress that can be recycled by a different shape after GC. That fix needs GC-root registration or hot-path content validation and is not deterministically reproducible; it's the higher-risk half and deserves its own PR.Summary by CodeRabbit
Object.definePropertychanges a property into a getter or redefined data property.