From 38e2fec1adba2838ace887c7b6e2e7bfa6b125ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 4 Oct 2026 02:22:55 +0200 Subject: [PATCH 1/2] fix(gc): root the raw handles #11815/#11843 added (raw_handle_debt back to baseline for these files) --- changelog.d/PENDING-raw-handle-11815-11843.md | 3 + .../construct/compiled_function.rs | 11 +-- .../class_registry/decl_prototype_birth.rs | 31 ++++---- .../field_get_set/class_object_props.rs | 2 +- .../field_get_set/class_object_template.rs | 18 +++-- .../class_object_template_tests.rs | 77 ++++++++++--------- 6 files changed, 77 insertions(+), 65 deletions(-) create mode 100644 changelog.d/PENDING-raw-handle-11815-11843.md diff --git a/changelog.d/PENDING-raw-handle-11815-11843.md b/changelog.d/PENDING-raw-handle-11815-11843.md new file mode 100644 index 0000000000..0e94912611 --- /dev/null +++ b/changelog.d/PENDING-raw-handle-11815-11843.md @@ -0,0 +1,3 @@ +### Fixed + +- Root the raw object handles the born-final class prototype builder, the class-object template and the birth-record mint read out of their handles, so those modules carry no raw-handle debt again. diff --git a/crates/perry-runtime/src/object/class_registry/construct/compiled_function.rs b/crates/perry-runtime/src/object/class_registry/construct/compiled_function.rs index d3c538fdf7..acbde22795 100644 --- a/crates/perry-runtime/src/object/class_registry/construct/compiled_function.rs +++ b/crates/perry-runtime/src/object/class_registry/construct/compiled_function.rs @@ -124,7 +124,7 @@ unsafe fn birth_record(proto: *const ObjectHeader) -> Option<(u32, u32, u32)> { /// it was minted from. #[cold] #[inline(never)] -unsafe fn mint_birth_record(func_value: f64, proto: *mut ObjectHeader) -> *mut ObjectHeader { +unsafe fn mint_birth_record(func_value: f64, proto: *mut ObjectHeader) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let proto_handle = scope.root_raw_mut_ptr(proto); let class_id = synthetic_class_id_for_function(func_value); @@ -146,7 +146,7 @@ unsafe fn mint_birth_record(func_value: f64, proto: *mut ObjectHeader) -> *mut O // GC_STORE_AUDIT(POINTER_FREE): a class id and a ShapeId, never a heap // reference. (*meta).instance_birth = u64::from(class_id) | u64::from(shape_id) << 32; - obj.get_raw_mut_ptr::() + obj.with_mut_ptr::(|o| crate::value::js_nanbox_pointer(o as i64)) } /// The class `class_id`'s registered prototype moved from `old` to another @@ -205,11 +205,12 @@ pub(super) unsafe fn construct_ordinary_compiled_function( } None => return None, }; - let obj = match birth_record(proto) { - Some((class_id, shape_id, slots)) => born_from_record(class_id, shape_id, slots), + let instance = match birth_record(proto) { + Some((class_id, shape_id, slots)) => { + crate::value::js_nanbox_pointer(born_from_record(class_id, shape_id, slots) as i64) + } None => mint_birth_record(func_handle.get_nanbox_f64(), proto), }; - let instance = crate::value::js_nanbox_pointer(obj as i64); Some(run_constructor_body( func_handle.get_nanbox_f64(), instance, diff --git a/crates/perry-runtime/src/object/class_registry/decl_prototype_birth.rs b/crates/perry-runtime/src/object/class_registry/decl_prototype_birth.rs index 66afa99ce1..18e0c10227 100644 --- a/crates/perry-runtime/src/object/class_registry/decl_prototype_birth.rs +++ b/crates/perry-runtime/src/object/class_registry/decl_prototype_birth.rs @@ -106,7 +106,7 @@ pub(super) fn decl_prototype_born_final(class_id: u32, parent_bits: u64) -> Opti // no meta record to mirror it in. It is fresh and unobserved, so the link // is not prototype surgery and invalidates nothing. let proto = scope.root_raw_mut_ptr(crate::object::js_object_alloc(class_id, count)); - if proto.get_raw_mut_ptr::().is_null() { + if proto.with_mut_ptr::(|p| p.is_null()) { return None; } let parent_bits = parent.get_heap_word_u64(); @@ -119,16 +119,18 @@ pub(super) fn decl_prototype_born_final(class_id: u32, parent_bits: u64) -> Opti ); } let proto_id = crate::object::shapes::object_proto_id_for(p, parent_bits); - let stamped = crate::object::shapes::stamp_linked_final_shape( - p, - list.get_raw_mut_ptr::(), - count, - proto_id, - parent_bits, - // Slot 0 is `constructor` (a class function object, no lane); - // every method slot names its body. - |slot| slot != 0, - ); + let stamped = list.with_mut_ptr::(|keys| { + crate::object::shapes::stamp_linked_final_shape( + p, + keys, + count, + proto_id, + parent_bits, + // Slot 0 is `constructor` (a class function object, no lane); + // every method slot names its body. + |slot| slot != 0, + ) + }); if stamped { crate::object::descriptor_state::note_attrs_born_with_keys(p as usize); } @@ -140,11 +142,12 @@ pub(super) fn decl_prototype_born_final(class_id: u32, parent_bits: u64) -> Opti return None; } crate::gc::runtime_shade_external_edge(parent_bits); - let proto = proto.get_raw_mut_ptr::(); - super::state::class_decl_prototype_object_root_store(class_id, proto); + proto.with_mut_ptr::(|p| { + super::state::class_decl_prototype_object_root_store(class_id, p) + }); #[cfg(test)] BORN_FINAL_BUILDS.with(|n| n.set(n.get() + 1)); - Some(crate::value::js_nanbox_pointer(proto as i64)) + Some(proto.with_mut_ptr::(|p| crate::value::js_nanbox_pointer(p as i64))) } #[cfg(test)] diff --git a/crates/perry-runtime/src/object/field_get_set/class_object_props.rs b/crates/perry-runtime/src/object/field_get_set/class_object_props.rs index 94fdef20db..092065f3a7 100644 --- a/crates/perry-runtime/src/object/field_get_set/class_object_props.rs +++ b/crates/perry-runtime/src/object/field_get_set/class_object_props.rs @@ -129,6 +129,7 @@ unsafe fn class_evaluation_prototype_value(obj: *const ObjectHeader) -> f64 { if let Some(parent_proto) = &parent_proto { if let Some(proto) = class.with_mut_ptr::(|class| { super::class_object_template::prototype_from_template( + &scope, class, class_id, parent_proto.get_heap_word_u64(), @@ -136,7 +137,6 @@ unsafe fn class_evaluation_prototype_value(obj: *const ObjectHeader) -> f64 { }) { CLASS_EVALUATION_PROTOTYPES_MATERIALIZED .store(true, std::sync::atomic::Ordering::Relaxed); - let proto = scope.root_raw_mut_ptr(proto); let proto_value = proto .with_mut_ptr::(|p| crate::value::js_nanbox_pointer(p as i64)); class.with_mut_ptr::(|class| { diff --git a/crates/perry-runtime/src/object/field_get_set/class_object_template.rs b/crates/perry-runtime/src/object/field_get_set/class_object_template.rs index b7b0abe48a..fdf08dd775 100644 --- a/crates/perry-runtime/src/object/field_get_set/class_object_template.rs +++ b/crates/perry-runtime/src/object/field_get_set/class_object_template.rs @@ -692,11 +692,12 @@ pub(crate) unsafe fn evaluation_method_value(class_id: u32, name: &str, class: f /// in the template's final prototype shape, linked to `parent_proto`. `None` /// when the template has no recorded prototype shape or that shape names /// another [[Prototype]]. -pub(crate) unsafe fn prototype_from_template( +pub(crate) unsafe fn prototype_from_template<'s>( + out: &'s crate::gc::RuntimeHandleScope, class: *mut ObjectHeader, class_id: u32, parent_proto: u64, -) -> Option<*mut ObjectHeader> { +) -> Option> { let cell = template_cell_of(class_id)?; let (field_count, final_shape, fills, proto_id) = cell.proto_template()?; if crate::object::shapes::stable_linked_proto_id(class_id, parent_proto) != Some(proto_id) { @@ -704,15 +705,16 @@ pub(crate) unsafe fn prototype_from_template( } #[cfg(test)] note_template_hit(true); - let scope = crate::gc::RuntimeHandleScope::new(); - let class = scope.root_raw_mut_ptr(class); - let parent = scope.root_heap_word_u64(parent_proto); - let proto = scope.root_raw_mut_ptr(crate::object::js_object_alloc(class_id, field_count)); + // Every handle lives in the caller's scope: the prototype handle is + // returned, and a handle cannot outlive the scope that rooted it. + let class = out.root_raw_mut_ptr(class); + let parent = out.root_heap_word_u64(parent_proto); + let proto = out.root_raw_mut_ptr(crate::object::js_object_alloc(class_id, field_count)); // The links `class_evaluation_prototype_value` makes, written into the // prototype's meta record directly: its evaluation (lexical owner) and its // [[Prototype]], whose identity the final shape already names. let (meta, _) = proto.across_mut::(|| { - crate::object::object_meta_ensure(proto.get_raw_mut_ptr::()) + proto.with_mut_ptr::(|p| crate::object::object_meta_ensure(p)) }); let owner = class.with_const_ptr::(|c| crate::value::js_nanbox_pointer(c as i64)); @@ -749,7 +751,7 @@ pub(crate) unsafe fn prototype_from_template( crate::object::slot_store::store_object_field_slot(proto, slot, bits.to_bits()) }); } - Some(proto.get_raw_mut_ptr::()) + Some(proto) } /// After `proto`, the prototype object of class object `class` (template diff --git a/crates/perry-runtime/src/object/field_get_set/class_object_template_tests.rs b/crates/perry-runtime/src/object/field_get_set/class_object_template_tests.rs index aa17718ae2..ac58bdaa73 100644 --- a/crates/perry-runtime/src/object/field_get_set/class_object_template_tests.rs +++ b/crates/perry-runtime/src/object/field_get_set/class_object_template_tests.rs @@ -108,16 +108,15 @@ fn later_evaluations_are_born_in_the_template_shapes() { let shape = |h: &crate::gc::RuntimeHandle<'_>| { h.with_mut_ptr::(|o| crate::object::shapes::object_shape_id(o)) }; - let c = classes - .iter() - .map(|h| h.get_raw_mut_ptr::()) - .collect::>(); - let p = protos - .iter() - .map(|h| h.get_raw_mut_ptr::()) - .collect::>(); + let addr = + |h: &crate::gc::RuntimeHandle<'_>| h.with_mut_ptr::(|o| o as usize); + let slot_of = |h: &crate::gc::RuntimeHandle<'_>, key: &[u8]| { + h.with_mut_ptr::(|o| slot(o, key)) + }; assert!( - c[0] != c[1] && p[0] != p[1] && p[1] != p[2], + addr(&classes[0]) != addr(&classes[1]) + && addr(&protos[0]) != addr(&protos[1]) + && addr(&protos[1]) != addr(&protos[2]), "one object per evaluation" ); assert_eq!(shape(&classes[0]), shape(&classes[1])); @@ -125,39 +124,43 @@ fn later_evaluations_are_born_in_the_template_shapes() { assert_eq!(shape(&protos[0]), shape(&protos[1])); assert_eq!(shape(&protos[1]), shape(&protos[2])); for i in 0..3 { - let s = slot(c[i], b"s"); - assert!( - static_method_value_runs(s, info, c[i]), - "s of evaluation {i} is at home in it" - ); - let m = slot(p[i], b"m"); - assert!( - static_method_value_runs(m, info, c[i]), - "m of evaluation {i} is at home in it" - ); - assert_eq!( - slot(p[i], b"constructor"), - crate::value::js_nanbox_pointer(c[i] as i64).to_bits(), - "prototype {i}'s constructor is its class object" - ); - assert_eq!( - super::super::class_registry::class_object_own_field_bytes( - c[i], - super::class_object_props::CLASS_EVALUATION_PROTOTYPE_KEY, - ) - .map(f64::to_bits), - Some(crate::value::js_nanbox_pointer(p[i] as i64).to_bits()), - "class object {i} links its own prototype" - ); + classes[i].with_mut_ptr::(|c| { + protos[i].with_mut_ptr::(|p| { + let s = slot(c, b"s"); + assert!( + static_method_value_runs(s, info, c), + "s of evaluation {i} is at home in it" + ); + let m = slot(p, b"m"); + assert!( + static_method_value_runs(m, info, c), + "m of evaluation {i} is at home in it" + ); + assert_eq!( + slot(p, b"constructor"), + crate::value::js_nanbox_pointer(c as i64).to_bits(), + "prototype {i}'s constructor is its class object" + ); + assert_eq!( + super::super::class_registry::class_object_own_field_bytes( + c, + super::class_object_props::CLASS_EVALUATION_PROTOTYPE_KEY, + ) + .map(f64::to_bits), + Some(crate::value::js_nanbox_pointer(p as i64).to_bits()), + "class object {i} links its own prototype" + ); + }) + }); } assert_ne!( - slot(c[1], b"s"), - slot(c[2], b"s"), + slot_of(&classes[1], b"s"), + slot_of(&classes[2], b"s"), "statics are per evaluation" ); assert_ne!( - slot(p[1], b"m"), - slot(p[2], b"m"), + slot_of(&protos[1], b"m"), + slot_of(&protos[2], b"m"), "methods are per evaluation" ); } From f9cce291f9d573882df753abdfd4b3eb5f7a89b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 4 Oct 2026 02:23:06 +0200 Subject: [PATCH 2/2] changelog: name the fragment for #11861 --- ...-raw-handle-11815-11843.md => 11861-raw-handle-11815-11843.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{PENDING-raw-handle-11815-11843.md => 11861-raw-handle-11815-11843.md} (100%) diff --git a/changelog.d/PENDING-raw-handle-11815-11843.md b/changelog.d/11861-raw-handle-11815-11843.md similarity index 100% rename from changelog.d/PENDING-raw-handle-11815-11843.md rename to changelog.d/11861-raw-handle-11815-11843.md