Skip to content

perf(runtime): delete the prototype-link flags; the ShapeId names its prototype identity kind (Refs #10507) - #11834

Merged
proggeramlug merged 4 commits into
mainfrom
perf-prototype-link-flags
Oct 3, 2026
Merged

proggeramlug merged 4 commits into
mainfrom
perf-prototype-link-flags

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Prototype-in-shape phase 2 (Refs #10507). With the prototype in the shape (#11815), the prototype-link flags were bookkeeping for the old two-sources model.

What changed

  • Deleted: PROTO_DIVERGED, USER_PROTO_OVERRIDE, CLASS_EVALUATION_PROTO and the USER_PROTO_OVERRIDE_EVER latch.
  • No meta record is allocated for prototype links any more: Object.create, __proto__ and setPrototypeOf objects.
  • instanceof walks the live chain from the shape, and the util.inherits escape hatch is gone.
  • The ShapeId now encodes its prototype-identity kind (plain / word / null) in bits 20–21, so reading an object's prototype is one compare in the common case; only a "word" identity reads the shape record.
  • CLASS_CHAIN_RELINKED_EVER still guards the class-id shortcuts. It goes away with the class-table retirement slices (perf: class prototypes hold real method slots (class-table retirement slice 1); pack relative relocations on Linux #11831 onward).

Instructions (main vs this PR)

Row Change
tsc −0.67% (−2.3% across 3 lld layout seeds)
Zod −1.9%
commander −0.1%
qs +0.13–0.16% (owner-accepted)
Row main this PR
instanceof 6-deep hit 1,857 610
instanceof 6-deep miss 1,363 585
instanceof 1-deep 352 181
g6 16,204 16,131
decimal_class 1,924 1,910
instanceof_class (function ctor) 178 181 (owner-accepted)

Verification: runtime 4847/0; codegen lib 1947/0; gap 370 with 0 regressions and 3 fixed; GC-stress seeds match; fmt clean. Verified on base c170908; this branch is the same commits replayed cleanly onto current main.

RSS: Zod is +4 MB on the default link with equal page-fault counts, which looks like huge-page placement (unproven). tsc RSS is −9 MB.

Ralph Küpper added 4 commits October 3, 2026 22:15
… prototype

With the prototype a fact of the shape (#11815), every reader of
OBJECT_META_FLAG_PROTO_DIVERGED was a cache gate whose key already carries
the prototype, so the per-instance flag only duplicated the ShapeId:

- store plans (field_set_by_name, its tail, the proxy set path) are keyed by
  (class id, key, receiver prototype bits);
- array-subclass dense layouts (array/subclass.rs, subclass_loop_guard.rs)
  are keyed by (class id, ShapeId) and validated by the receiver word, which
  carries the ShapeId;
- the defineProperty key-add tail (keys_array.rs) replays ShapeId-keyed
  transition edges;
- `x instanceof <class object>` (instanceof/dynamic_dispatch.rs) now asks
  whether the receiver has a recorded prototype instead.

The flag, its funnel write, the class-object-template set/clear and its
tests are deleted; bit 0 of ObjectMeta.flags is free. A runtime-wiring link
on a function-constructor instance no longer allocates a meta record.

Tests: `new F()` and `Object.create(F.prototype)` receivers share one shape;
the dense array-subclass test now asserts the receiver moves to another
ShapeId across a prototype override and its own element still reads the same
(it asserted the old per-instance decline).

Refs #10507
…ink flags

The user-override and class-evaluation flags (and the process-wide
USER_PROTO_OVERRIDE_EVER latch) recorded HOW an object's prototype was set.
With the prototype a fact of the shape, every reader now asks the shape what
the prototype IS:

- "does this instance stand on a prototype other than its class's?" is the
  shape identity versus the class's implied identity
  (`object_has_individual_class_prototype`: prototype_override field reads,
  native_call_method, symbol gets, evaluation-template misses). A compiled
  class instance linked to its own declaration prototype (runtime wiring of a
  native-base subclass) takes the class identity, so it keeps the class
  surface and its class's shapes.
- "was a class declaration prototype re-pointed?" compares its recorded
  [[Prototype]] with its declaration's parent prototype
  (`decl_prototype_relinked`), behind a latch armed only by user relinks of
  class-chain links (a declaration prototype, a class object or constructor,
  a function object); re-pointing an ordinary instance arms nothing.
- `instanceof` against a class walks the live chain from the shape when the
  receiver's identity is not its class's or a class-chain link was relinked;
  the util.inherits escape hatch is gone (the relink is a shape fact of the
  prototype it moved).

The prototype funnel records no flag and so allocates no meta record:
Object.create, `__proto__` and setPrototypeOf receivers carry their
prototype in their shape alone. A receiver that already has a meta record
keeps the same bits there. ObjectMeta.flags bits 0, 3 and 4 are free.

Refs #10507
…e record only for a word

Phase 2 made every meta-less receiver read its [[Prototype]] from its shape
record and identity word (~50 instructions), where main answered a meta-less
non-synthetic receiver with one compare of its class id. qs (+0.75%),
commander (+0.29%), g6 (+7.4%), `instanceof` against a class (+21%) and
decimal_class paid for it.

- A ShapeId carries its prototype identity KIND in bits 20-21: plain (the
  realm default, a class's declaration prototype, a per-object identity),
  word (a serial, MIXED or UNIQUE link) or null. Each band mints every kind
  from its own counter, in alternating 2^20-id granules (page-aligned, so no
  record memory is spent on the interleave); the compiler's static band is
  one plain granule. `ShapeSlab::insert` asserts that a plain or null id
  names exactly that identity.
- `object_prototype_word` (now inlined): the meta word, else 0 for a plain
  id, the born-null header test for a null id, and the record plus identity
  word (out of line) only for a word id.
- `object_has_individual_class_prototype` and the `instanceof` relink check
  test the kind before any record or registry read; `instanceof` hands the
  check the receiver it already holds.
- `object_proto_id_for` asks the class registry about a declaration
  prototype only when the recorded prototype is an object of that class.
- The inherited-read walks (`read_holder::walk`, `class_link`,
  `class_read::admitted_next`) read each hop's word once for both the
  identity check and the hop, not two or three times.
- An object marked as a prototype keeps its own [[Prototype]] in its meta
  record (`keep_prototype_in_record`): a class declaration prototype is
  linked to its parent's before it has a record.

Refs #10507
@proggeramlug
proggeramlug merged commit 84ecded into main Oct 3, 2026
24 checks passed
@proggeramlug
proggeramlug deleted the perf-prototype-link-flags branch October 3, 2026 20:16
@coderabbitai

coderabbitai Bot commented Oct 3, 2026

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b4b07d97-07d0-4c3b-ba6b-d102bc5a2689
📥 Commits

Reviewing files that changed from the base of the PR and between 644fcca and d268527.

📒 Files selected for processing (36)
  • changelog.d/PENDING-prototype-link-flags.md
  • crates/perry-runtime/src/array/subclass.rs
  • crates/perry-runtime/src/array/subclass_loop_guard.rs
  • crates/perry-runtime/src/array/subclass_tests.rs
  • crates/perry-runtime/src/gc/tests/dead_owner_side_tables/meta_and_shape_records.rs
  • crates/perry-runtime/src/json/stringify_tojson_probe.rs
  • crates/perry-runtime/src/json/stringify_tojson_probe_tests.rs
  • crates/perry-runtime/src/object/class_constructors.rs
  • crates/perry-runtime/src/object/class_registry.rs
  • crates/perry-runtime/src/object/class_registry/prototype_methods.rs
  • crates/perry-runtime/src/object/class_registry/prototype_objects.rs
  • crates/perry-runtime/src/object/class_registry/state.rs
  • crates/perry-runtime/src/object/class_super_chain.rs
  • crates/perry-runtime/src/object/field_get_set/class_object_template.rs
  • crates/perry-runtime/src/object/field_set_by_name.rs
  • crates/perry-runtime/src/object/field_set_by_name/tail.rs
  • crates/perry-runtime/src/object/instanceof.rs
  • crates/perry-runtime/src/object/instanceof/dynamic_dispatch.rs
  • crates/perry-runtime/src/object/instanceof/static_dispatch.rs
  • crates/perry-runtime/src/object/meta_flags.rs
  • crates/perry-runtime/src/object/method_site/read_holder.rs
  • crates/perry-runtime/src/object/method_site/read_holder/class_read.rs
  • crates/perry-runtime/src/object/mod.rs
  • crates/perry-runtime/src/object/object_ops/keys_array.rs
  • crates/perry-runtime/src/object/property_key.rs
  • crates/perry-runtime/src/object/proto_validity.rs
  • crates/perry-runtime/src/object/prototype_chain.rs
  • crates/perry-runtime/src/object/shapes.rs
  • crates/perry-runtime/src/object/shapes_prototype_tests.rs
  • crates/perry-runtime/src/object/shapes_slot_list.rs
  • crates/perry-runtime/src/object/shapes_store.rs
  • crates/perry-runtime/src/object/shapes_store_tests.rs
  • crates/perry-runtime/src/object/shapes_tests.rs
  • crates/perry-runtime/src/object/static_shapes.rs
  • crates/perry-runtime/src/proxy.rs
  • crates/perry-runtime/src/symbol/get.rs
 _______________________________________________________________________________________________________________________________________
< Test your estimates. Mathematical analysis of algorithms doesn't tell you everything. Try timing your code in its target environment. >
 ---------------------------------------------------------------------------------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant