Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions changelog.d/11540-object-create-ordinary-birth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Fixed a ~1,300 instructions-per-store regression on `Object.create(proto)`
receivers. Since `Object.create` stopped minting a synthetic class id per call
(#11166), its result is class-less, and nothing marked it an ordinary object, so
the static-key store site's receiver-kind test refused it: every `o.k = v`
missed the site cache and took the full `[[Set]]` walk, even for an own data
property. `Object.create` now births its result ordinary, like the other
ordinary birth sites, and the store site publishes its shape as it does for a
literal or a class instance. On the acceptance matrix the `Object.create`
overwrite cell goes from 1,588 back to about 300 instructions per store.
12 changes: 11 additions & 1 deletion crates/perry-runtime/src/object/object_ops/prototype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,17 @@ pub extern "C" fn js_object_create(proto_value: f64) -> f64 {
}
let scope = crate::gc::RuntimeHandleScope::new();
let proto = scope.root_nanbox_f64(proto_value);
let obj = scope.root_raw_mut_ptr(js_object_alloc(0, 0));
let born = js_object_alloc(0, 0);
// `OrdinaryObjectCreate(proto)`: the result is an ORDINARY object, and its
// [[Prototype]] becomes a fact of its shape in the link below (#11342).
// So it is born ordinary like every other ordinary birth site
// (`mark_object_plain_ordinary`): the store sites' receiver-kind test then
// admits it on its ShapeId alone, exactly as it admits a literal or a
// class instance. Unmarked, a class-less receiver fails that test on every
// store and takes the full `[[Set]]` walk (#11166 moved Object.create off
// its synthetic class id, which had been admitting it).
unsafe { crate::object::mark_object_plain_ordinary(born) };
let obj = scope.root_raw_mut_ptr(born);
// The link is a self-rooting entry point: it roots the owner and the
// prototype before its meta-record allocation, so the handle is re-read
// afterwards for the post-collection address.
Expand Down
41 changes: 41 additions & 0 deletions crates/perry-runtime/src/proxy/put_value/packed_set_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,3 +516,44 @@ fn a_dictionary_receiver_never_publishes_a_store_site_word() {
"a dictionary receiver must not publish"
);
}

/// An `Object.create(proto)` receiver is an ORDINARY object whose prototype is
/// a fact of its shape (#11342), so a store site publishes its shape exactly
/// as it publishes a literal's or a JSON object's. #11166 moved Object.create
/// off its synthetic class id onto `class_id == 0`; unmarked, the receiver
/// failed the receiver-kind test on every store and took the full `[[Set]]`
/// walk (the acceptance matrix's ocreate column went 293 -> 1,588 instr/op).
#[test]
fn an_object_create_receiver_publishes_its_shape_and_inline_slot() {
let proto = parsed(br#"{"pa":32}"#);
let target = crate::object::js_object_create(proto);
let obj = object_of(target);
assert_eq!(
unsafe { (*obj).class_id },
0,
"test premise: Object.create yields a class-less receiver"
);
let a = interned(b"a");
let b = interned(b"b");
// Both keys land in the birth-floor inline slots (the key-adds are what
// the fixture's `t.a = 1; t.b = 2` performs).
store_fresh(target, a, 1.0);
store_fresh(target, b, 2.0);
let (stored, word) = store_fresh(target, b, 5.0);
assert_eq!(stored, 5.0, "the miss performs the store");
assert_eq!(
word as u32,
stamp(target),
"an Object.create receiver must publish its ShapeId to the site word"
);
assert_eq!(word >> 32, 1, "high half: `b` is the second own slot");
// The emitted hit's per-object half admits it too, so the published
// word is actually served inline rather than missing on every store.
assert!(
unsafe { packed_hit_receiver_ok(obj) },
"the emitted hit's receiver-kind test must admit an Object.create receiver"
);
// Its prototype is still the one it was created with.
let got = crate::object::js_object_get_prototype_of(target);
assert_eq!(got.to_bits(), proto.to_bits());
}
Loading