Skip to content

is_registered_class_prototype_object is a linear scan too — same slope #9214 just removed, on the line immediately before it #9225

Description

@proggeramlug

Summary

is_registered_class_prototype_object (class_registry/parent_static.rs:1742) answers "is this object some class's registered prototype?" with map.values().any(…) over CLASS_PROTOTYPE_OBJECTS. It sits in the || term immediately before the scan #9214 replaces, in the same predicate — so with #9214 merged, this becomes the remaining half of the same cost.

Measured on the fixed (#9214) build, defineProperty against non-prototype receivers:

synthetic prototypes 0 100 400
ns 1035 1090 1265

+0.575 ns/entry — essentially the slope #9214 removed, still there. It also explains the residual delete slope (+0.657 ns/entry) that survives #9214.

Why the table grows

CLASS_PROTOTYPE_OBJECTS mints a synthetic class id per distinct F.prototype = obj (prototype_objects.rs:293) — i.e. one per ES5-transpiled constructor in a bundle. Any downlevelled dependency graph populates it linearly with its own size, which is exactly the regime where an O(n) scan per property operation stops being free.

Why it is NOT the same fix as #9214

#9214's table can use abandon-on-repoint: its forward map is injective and never re-pointed, so the one place that could break the inverse (insert) checks it and permanently falls back to the authoritative scan.

This table IS re-pointedprototype_objects.rs:272 and object_ops/prototype.rs:117 both rewrite an existing entry. So an inverse here needs rebuild-on-repoint, not abandon-on-repoint, or it degrades to the scan the first time any program reassigns a .prototype — which is the common case rather than the rare one.

Everything else from #9214 transfers directly, and should be reused rather than reinvented: privacy so no seventh writer can exist, the degraded mode being the pre-existing scan verbatim, the debug_assert_eq! against that scan on every lookup, and the GC visit helpers that re-key from what the visitor left behind.

A false comment to delete while in here

disable_inline_guards_for_descriptor_target documents itself as: descriptor installs are rare and "never on the hot property path, so the scan cost is acceptable".

esbuild's __export(exports, {…}) falsifies that for every bundle — claude-code's has 1,526 Object.defineProperty occurrences. The comment is the reason the scan was left in place, and it should not outlive the premise.

Instrument

The slope table above is the test: a fix must flatten ns-per-op against synthetic-prototype count, not merely lower the constant. #9214's differential harness and its sabotage check are directly reusable.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions