perf(runtime): a Bloom filter for the heap-allocated registries, and #9225's linear scan gated — cc --help −1.25% instructions, −2.37% cycles - #9291
Conversation
Hoist PerryTS#9177's symbol address range out of is_registered_symbol_slow into is_registered_symbol as a RegistryAddrWindow, so the common negative answer costs no call; add the same window to is_uint8array_buffer. Both rejections are re-derived from the authoritative tables under debug_assertions. Not yet measured on cc --help.
…lass-prototype probes Round 2 (PerryTS#9272) put an inline [lo, hi] address window in front of the buffer and typed-array probes. Measured against the four probes it named as follow-up, a window is the wrong shape for two of them and the right shape for one: is_registered_symbol 378,163 calls, window rejects 38.3% is_registered_class_prototype_object 26,290 calls, window rejects 54.0% is_uint8array_buffer 537,921 calls, window rejects 100% Symbols and class prototypes are ordinary GC-heap objects, so [lo, hi] grows to cover most of the heap. RegistryAddrFilter is the same monotone contract over a 1024-bit Bloom filter instead of a range; replaying each probe's real argument stream from a cc --help run, it rejects 99.58% and 99.05%. is_uint8array_buffer keeps the cheaper window (100% rejection, 0 true answers). Every rejection is re-derived from the authoritative table under debug_assertions, so a registration route added without admitting panics in the first test that touches it.
…Uint8Array window
…he latch' Two sabotage checks in other suites defeat a cheaper upstream screen and require this counter to move; counting filter admissions instead made them fail. Filter admissions get their own counter, mirroring typedarray::TEST_TA_WINDOW_ADMITTED_PROBES.
…prototype probe too
The rejection path never took either lock, so a blocking audit could hang on a caller the audited code would not have. Sabotage-checked: removing the admit from either registration funnel fails 1 test (symbol) and 3 tests (class prototype), so the audits demonstrably run.
…filter', not 'range'
…tables); record the end-of-run false-positive rate
They are fmt-dirty on pristine main (42d0f45) from PerryTS#9274/PerryTS#9279; a stray `cargo fmt --all` picked them up. Reported separately, not fixed here.
…ic.rs parent_static.rs was at 1992 lines on main and this PR adds 52, crossing the 2000-line cap. Extracts the inline shape_authority_tests_8067 module to a sibling under parent_static/; body unchanged.
0b9c66d to
4654565
Compare
|
Warning Review limit reachedNext included review available in 32 seconds. View limit detailsLimit details: You’ve used all 8 included reviews currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (16)
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, with one file split pushed onto the branch. This is the best-handled PR in this family, and the reason is specific. It is the fifth registry-probe change; I found real defects in the first two, and both were the same shape — a "the writer set is complete" claim that wasn't. #9176 armed its latch on one of two insert paths into the external-Uint8Array registry; #9177 didn't widen its range when the collector re-keys a moved symbol. This PR closes that class three separate ways:
Verified by sabotage rather than by reading, and the first attempt is worth reporting because it was worthless: I removed the So the filter's coverage of the re-key path is verified, not asserted. A sabotage that doesn't fail proves nothing, and I nearly logged the first one as a pass. On the measurement, the part I'd single out is that it cancelled half its own premise: the uretprobe counts show What I fixed: Validation: |
Follow-up to #9272 (merged), covering the four probes it named as the largest remaining members of the family. Also closes the perf half of #9225.
Cycles fall by roughly twice instructions and IPC rises.
--helpoutput byte-identical to node (9,175 bytes, rc=0) in all three arms. Binary +24,856 bytes (+0.007%).Measurement cancelled half the premise
Exact uprobe/uretprobe counts on one
cc --helprun:is_registered_symbol_slowis_uint8array_buffer_slowis_registered_class_prototype_objectis_registered_box_ptris_registered_box_ptris not a member of this family and is deliberately untouched. Its four callers (js_closure_set_box_capture_ptr,js_box_get_bits,js_box_set_bits,js_box_capture_cell_ptr— 100% between them) ask it about actual box pointers on the async-locals path. Any rejection filter could remove at most 2.6% of its 0.22%. If it is attacked later the target is the cost of the hit — a positive cache — not a filter.And #9272's address window is the wrong shape for two of the others. Symbols and class prototypes are ordinary
gc_malloc'd heap objects, so[lo, hi]spans ~280 MB of heap. Replaying each probe's real argument stream against the window its own registrations would build:That is also why #9177's symbol range only ever removed 38% and the rest still paid the process-global mutex.
The change
RegistryAddrFilterbeside #9272's window, same monotone contract (admit before publish, bits only ever set,false= definitively absent), 1,024 bits, k=3,AcqRelfetch_orwith no pre-check — for the reason #9272 documents: a thread that skips the RMW performs no acquire.[lo,hi]range and moves the test inline intois_registered_symbol, so a negative costs no call, noOnceLock, no mutex.PERRY_SYMBOL_RANGE_FILTERremoved: it guarded an enumeration, and the machine-checked audit below is strictly stronger.map.values().any(…)linear scan (is_registered_class_prototype_object is a linear scan too — same slope #9214 just removed, on the line immediately before it #9225), admitted at all five address-introducing sites, including the two GC root scanners and the per-slot GC step that re-key addresses throughvisit_usize_slot.__exportfalsifies for every bundle.Attribution, 100% of calls accounted
proxy::reflect_value_is_object52.8%,js_is_symbol25.6%,dispatch_primitive9.5%; top 12 = 99.4%. No caller-side fix exists.descriptor_state::disable_inline_guards_for_descriptor_target, which genuinely needs the answer, so the fix belongs below it.typed_array_addr_from_value77.7%.Safety: machine-checked, then sabotaged
Every rejection is re-derived from the authoritative table under
debug_assertions. That is not left as an assertion — it is proved able to fail: deleting theadmitfrom the symbol funnel fails 1 test, from the class-prototype funnel fails 3, and forcingmay_containtotruefails 5.Audits use
try_lock/try_read: the rejection path never took those locks, so a blocking audit would introduce a deadlock the audited code cannot have. Verified compiled out of release —grepfor the panic strings in the shipped binaries returns 0 hits.Answer census re-run on the shipped binary: symbol 378,163 → 1,492 calls with all 622 genuine "yes" preserved (870 false positives, 0.23%); uint8array 537,921 → 0; class prototype 122 yes / 26,168 no, identical to baseline.
2,893 runtime tests pass, plus 14 targeted integration tests (decl-prototype reverse lookup, class-prototype assignment, user
Symbol.iterator,setPrototypeOfchain).global_sink_isolation.py,check_thread_locals.py,check_gc_scanner_latches.pyOK.One property to keep in view
The filter's bits accrue per admission, not per live entry — the collector re-keys both tables, so an evacuated symbol is admitted again at its new address. Saturation degrades to today's behaviour, never to a wrong answer, and
WORDSis the knob. The number to watch is the end-of-run false-positive rate (0.23% here), not the registration count.Unrelated, noted
cargo fmt --all -- --checkfails on pristine main (42d0f45685) — 6 hunks inperry-codegen/src/stmt/loops.rsandstable_packed_accumulator.rsfrom #9274/#9279, reproduced on a second machine with the pinned nightly. This branch does not touch those files.