From 9d5a014cde6c2a3572f5871d4a6b6573a63b9bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 27 Sep 2026 03:42:47 +0000 Subject: [PATCH 1/2] fix(runtime): Object.create births its result ordinary, so store sites publish its shape Since #11166 an Object.create result is class-less (class_id 0) and nothing marked it ordinary, so the static-key store site's receiver-kind test refused it: every o.k = v on such a receiver missed the site cache and took the full [[Set]] walk through js_put_value_set_packed_miss, even for an own data property. The acceptance matrix's ocreate column moved ~1,300 instr/op (overwrite 293 -> 1,588). OrdinaryObjectCreate yields an ordinary object whose [[Prototype]] is a fact of its shape (#11342), so it is born ordinary like the other ordinary birth sites, and the store site publishes its ShapeId as for a literal or a class instance. Regression test: the site word is primed from an Object.create receiver and the emitted hit's receiver-kind half admits it (fails with the mark removed). --- changelog.d/object-create-ordinary-birth.md | 9 ++++ .../src/object/object_ops/prototype.rs | 12 +++++- .../src/proxy/put_value/packed_set_tests.rs | 41 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 changelog.d/object-create-ordinary-birth.md diff --git a/changelog.d/object-create-ordinary-birth.md b/changelog.d/object-create-ordinary-birth.md new file mode 100644 index 0000000000..ed6f2802b0 --- /dev/null +++ b/changelog.d/object-create-ordinary-birth.md @@ -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. diff --git a/crates/perry-runtime/src/object/object_ops/prototype.rs b/crates/perry-runtime/src/object/object_ops/prototype.rs index 7107dd4fd6..806fd57cde 100644 --- a/crates/perry-runtime/src/object/object_ops/prototype.rs +++ b/crates/perry-runtime/src/object/object_ops/prototype.rs @@ -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. diff --git a/crates/perry-runtime/src/proxy/put_value/packed_set_tests.rs b/crates/perry-runtime/src/proxy/put_value/packed_set_tests.rs index 7a714912d0..b100344d16 100644 --- a/crates/perry-runtime/src/proxy/put_value/packed_set_tests.rs +++ b/crates/perry-runtime/src/proxy/put_value/packed_set_tests.rs @@ -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()); +} From f5f54088933b532c1b82640450bfe66b31112c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 27 Sep 2026 12:08:45 +0000 Subject: [PATCH 2/2] changelog: name the fragment after PR #11540 --- ...te-ordinary-birth.md => 11540-object-create-ordinary-birth.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{object-create-ordinary-birth.md => 11540-object-create-ordinary-birth.md} (100%) diff --git a/changelog.d/object-create-ordinary-birth.md b/changelog.d/11540-object-create-ordinary-birth.md similarity index 100% rename from changelog.d/object-create-ordinary-birth.md rename to changelog.d/11540-object-create-ordinary-birth.md