Repository navigation
fix(runtime): the shape is the one record of [[Prototype]] (null links, function links) - #13666
Merged
Merged
Conversation
added 3 commits
November 1, 2026 14:34
…der bit The link had two records: the shape's identity and OBJ_FLAG_NULL_PROTO, a header bit set at birth that the identity was derived from and ~20 readers tested directly. The bit was sticky across setPrototypeOf, a later null link never set it, and a declared-keys class family tagged null (require.cache) was registered with its class's identity while its header said null. So readers disagreed: a thenable on a relinked born-null object was not followed, util.inspect called a relinked object "null prototype" and missed setPrototypeOf(x, null), and require.cache inherited Object.prototype's methods. The shape is now the one record. A born-null birth publishes its first shape with the null identity passed in; a layout tagged null registers the null identity for class families too; a module namespace takes its null link through the prototype funnel. Every reader asks object_prototype_is_null / cell_prototype_is_null (the recorded prototype, else the shape), which answer TAG_NULL for any null link. The bit, its store-lane gates and its realm-image header word are gone; 0x40 is free.
Object.setPrototypeOf(fn, p) wrote the link as a "p" key in the function's
internal state record and moved the function to the shared FunctionDictionary
shape. Every read of a relinked function (ArkType's callable class instances)
then searched that record by key bytes and lost every shape fast path, and
the readers that never looked there answered from Function.prototype.
A described function's shape already names its [[Prototype]] (its body
kind's intrinsic, by reserved serial, now derived from the body kind without
a record read). A link now moves it to the Function shape of its own keys
with the linked identity, the class-less rule an ordinary receiver takes (the
prototype gets its stable serial first); a key add carries the identity its
current shape names. The record keeps the link only for a FunctionDictionary
function, which answers nothing about it ("ask the object"), and a described
link moves into the record when the function becomes a dictionary. The bag
stays a null-chain slot store, so no own-property writer changes meaning.
The readers follow the one link: closure_static_prototype and
object_static_prototype (a function answers from its own link, never its
property bag's record), the layout probe, instanceof (a linked function's
chain is walked for `C` and for `Function`, and its @@hasInstance is read
from that chain when a hop may own a symbol key), `constructor` and a null
link's members (undefined, not Function.prototype's). Stores onto a bound
function take the function store lane, which checks the chain.
Also: a program's first inherited read of a function (`fn.toString`)
answered undefined because %Function.prototype% was not built yet; the read
now builds the realm global that holds it.
|
Caution Review failedThe pull request is closed.
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. |
added 2 commits
November 1, 2026 15:37
…authority # Conflicts: # crates/perry-runtime/src/object/instanceof/static_dispatch.rs
Contributor
Author
|
Head 7766f51 merges main, including #13546. In the Verification of 7766f51 (qb9):
|
proggeramlug
marked this pull request as ready for review
November 1, 2026 16:04
proggeramlug
pushed a commit
that referenced
this pull request
Nov 2, 2026
Since #13666 a null [[Prototype]] is the shape's identity, not header bit 0x40; the derivation emits no such bit and three producer tests still pinned it. They now assert the birth facts: null_prototype and the absent prototype with the header word of the ordinary birth of the same members, callable as the layout's header bit, the frozen word from the ABI constants. The GcHeader bit table lists bit 6 as free.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The shape becomes the one record of [[Prototype]]: for a null link (part 1) and for a function's link (part 2). This follows #13648, which made a callable's inherited read one OrdinaryGet.
Part 1: a null [[Prototype]] is the shape's identity, not a header bit
A null prototype was recorded twice. The shape's identity carried it, and so did
OBJ_FLAG_NULL_PROTO, a header bit set at birth. The identity was derived from that bit, and about 20 readers tested the bit directly. The bit stuck acrosssetPrototypeOf, a later null link never set it, and the declared-keys familyrequire.cache(tagged null) was registered with its class's identity.Now:
object_prototype_is_null/cell_prototype_is_null.A null-chain store answers "nothing intercepts" without walking the chain. The JSON paths ask
object_static_prototype_is_linked, so a null chain reads like the default chain. A property bag's holder slot, which holds its owner's [[Prototype]], is never given the bag's own null.Bugs fixed on main (test
test_gap_null_prototype_shape_authority,test_gap_null_prototype_require_cache):Object.create(null)object was not followed;util.inspectcalled an object relinked toObject.prototype"null prototype", and missedsetPrototypeOf(x, null);require.cacheinheritedObject.prototype's methods.Part 2: a function's [[Prototype]] is its shape's identity
Object.setPrototypeOf(fn, p)moved the function to the shared FunctionDictionary shape and stored the link as a key in its state record. Now the function moves to the Function shape of its own keys, with the linked identity. That is the class-less rule an ordinary object takes; the prototype gets its stable serial first. The record holds the link only for a FunctionDictionary function.The readers follow the one link:
closure_static_prototype/object_static_prototype: a function answers from its own link, never from its bag's record;instanceof C,instanceof Functionand@@hasInstanceon the linked chain;constructorand the members of a null link.Stores onto a bound function take the function store lane, which checks the chain. A function's base identity comes from its body kind, without a record read.
Bugs fixed on main (tests
test_gap_function_link_on_shape,test_gap_function_first_inherited_read,test_gap_prototype_link_fast_paths):fn instanceof Candfn instanceof Functionignored the link;toString/constructorfromFunction.prototype;@@hasInstanceon the linked chain was ignored;fn.toStringread answeredundefined.Verification (qb9)
cargo test -p perry-runtime --lib(RUST_TEST_THREADS=1): 6393/0, same as main.layouts_and_native_identity_are_pinnedfails, as it does on main.ark.js: 4.04 G → 3.94 G.--version/--help: still running; I'll post the results in a comment.Draft: hold for review.