From ad7a633e254d18e38c29601f0cdd6bb5027edc1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 3 Oct 2026 22:27:27 +0000 Subject: [PATCH] fix(runtime): a class function object keeps its evaluation state and its prototype link in separate captures #11840 put the evaluation state in capture 1 of the class function object and #11843 put the prototype link there too; the merge left conflict markers in class_value.rs and main does not compile. The evaluation state keeps capture 1 (codegen writes it at CLASS_EVALUATION_STATE_CAPTURE); the prototype link moves to capture 2, and the object is minted with three captures. Const asserts keep the two slots apart. --- changelog.d/class-value-capture-conflict.md | 6 +++ .../perry-runtime/src/object/class_value.rs | 39 +++++++------------ 2 files changed, 21 insertions(+), 24 deletions(-) create mode 100644 changelog.d/class-value-capture-conflict.md diff --git a/changelog.d/class-value-capture-conflict.md b/changelog.d/class-value-capture-conflict.md new file mode 100644 index 0000000000..d0161a68b6 --- /dev/null +++ b/changelog.d/class-value-capture-conflict.md @@ -0,0 +1,6 @@ +**runtime: a class function object keeps its evaluation state and its prototype link in separate captures (fixes the main build)** + +#11840 and #11843 both claimed capture 1 of the class function object (the +evaluation state and the prototype link); their merge left conflict markers in +`class_value.rs`. The evaluation state keeps capture 1 (codegen writes it), the +prototype link moves to capture 2, and the object is minted with three captures. diff --git a/crates/perry-runtime/src/object/class_value.rs b/crates/perry-runtime/src/object/class_value.rs index 4ca0416702..935a3f6fb3 100644 --- a/crates/perry-runtime/src/object/class_value.rs +++ b/crates/perry-runtime/src/object/class_value.rs @@ -242,7 +242,7 @@ const CLASS_EVALUATION_STATE_SLOT: usize = crate::codegen_abi::CLASS_EVALUATION_ /// never minted has no evaluation to protect. pub(crate) fn class_value_is_first_evaluation(class_id: u32) -> bool { class_value_cached(class_id).is_some_and(|closure| { - // SAFETY: a live class function object minted with two capture slots. + // SAFETY: a live class function object minted with its capture slots. unsafe { *crate::closure::closure_capture_slots_mut(closure).add(CLASS_EVALUATION_STATE_SLOT) == crate::codegen_abi::CLASS_FIRST_EVALUATION_STATE @@ -254,11 +254,8 @@ pub(crate) fn class_value_is_first_evaluation(class_id: u32) -> bool { /// old generation and pinned (it lives as long as the agent and never moves), /// code pointer /// [`js_class_constructor_called`], capture slot 0 = the class id as INT32, -<<<<<<< 07fa0cd9d0e1faa312e5aeddb7155bd773cb945a -/// capture slot 1 = its evaluation state ([`CLASS_EVALUATION_STATE_SLOT`]). -======= -/// capture slot 1 = the class's `prototype` object once it exists. ->>>>>>> f5aaf5308057b887dc30478f6116f585715b43a8 +/// capture slot 1 = its evaluation state ([`CLASS_EVALUATION_STATE_SLOT`]), +/// capture slot 2 = the class's `prototype` object once it exists. /// /// Never collects: callers hold raw receiver pointers across the lookup, so /// the old-arena allocation runs under a [`crate::gc::GcSuppressScope`]. @@ -270,29 +267,19 @@ fn class_value_mint(class_id: u32) -> *mut ClosureHeader { "a class function object belongs to a compiled class id, never a builtin or synthetic band: {class_id:#x}" ); let _no_collect = crate::gc::GcSuppressScope::new(); -<<<<<<< 07fa0cd9d0e1faa312e5aeddb7155bd773cb945a - let payload = crate::closure::closure_payload_size(2); -======= let payload = crate::closure::closure_payload_size(CLASS_VALUE_CAPTURES); ->>>>>>> f5aaf5308057b887dc30478f6116f585715b43a8 let ptr = crate::arena::arena_alloc_gc_old_born_tenured( payload, std::mem::align_of::(), crate::gc::GC_TYPE_CLOSURE, ) as *mut ClosureHeader; unsafe { -<<<<<<< 07fa0cd9d0e1faa312e5aeddb7155bd773cb945a - // GC_STORE_AUDIT(INIT): fresh class function object; both captures - // are INT32s (class id, evaluation state) and the props edge is null — - // pointer-free. - (*ptr).capture_count = 2; -======= - // GC_STORE_AUDIT(INIT): fresh class function object; capture 0 is an - // INT32 class id, capture 1 (the prototype link) starts `undefined` - // and the props edge is null — pointer-free. The link's later pointer - // is a root slot of the class-value scan, like `props`. + // GC_STORE_AUDIT(INIT): fresh class function object; captures 0 and 1 + // are INT32s (class id, evaluation state), capture 2 (the prototype + // link) starts `undefined` and the props edge is null — pointer-free. + // The link's later pointer is a root slot of the class-value scan, + // like `props`. (*ptr).capture_count = CLASS_VALUE_CAPTURES as u32; ->>>>>>> f5aaf5308057b887dc30478f6116f585715b43a8 (*ptr).shape_id = crate::closure::shape::function_class_shape(); (*ptr).info = &CLASS_CONSTRUCTOR_INFO; (*ptr).props = std::ptr::null_mut(); @@ -829,11 +816,15 @@ pub(crate) fn class_value_ptr(class_id: u32) -> *mut ClosureHeader { } } -/// Captures of a class function object: the class id, then the prototype link. -const CLASS_VALUE_CAPTURES: usize = 2; +/// Captures of a class function object: the class id, the evaluation state +/// ([`CLASS_EVALUATION_STATE_SLOT`]), then the prototype link. +const CLASS_VALUE_CAPTURES: usize = 3; /// The capture holding the class's `prototype` object (NaN-boxed), or /// `undefined` before it exists. -const CLASS_PROTOTYPE_LINK_CAPTURE: usize = 1; +const CLASS_PROTOTYPE_LINK_CAPTURE: usize = 2; +const _: () = assert!(CLASS_PROTOTYPE_LINK_CAPTURE != CLASS_EVALUATION_STATE_SLOT); +const _: () = assert!(CLASS_EVALUATION_STATE_SLOT < CLASS_VALUE_CAPTURES); +const _: () = assert!(CLASS_PROTOTYPE_LINK_CAPTURE < CLASS_VALUE_CAPTURES); /// The prototype-link word of class function object `closure`. ///