diff --git a/changelog.d/6963-shape-keyed-typed-layout-queries.md b/changelog.d/6963-shape-keyed-typed-layout-queries.md new file mode 100644 index 0000000000..876503ac73 --- /dev/null +++ b/changelog.d/6963-shape-keyed-typed-layout-queries.md @@ -0,0 +1 @@ +**GC / typed layouts:** restore the typed-shape descriptor lookups for shape-keyed objects. #6893 moved the canonical `TypedLayoutDescriptor` of every class instance into the shape-keyed `SHAPE_LAYOUTS` map and deleted the per-object entry, but four query helpers kept probing `TYPED_LAYOUTS` alone — so every class-field typed guard deopted, `PERRY_VERIFY_TYPED_INTACT=1` aborted on any class instance, and one FFI INT32 store permanently evicted an object's typed descriptor. Fixes the 33 red `perry-runtime` tests that had `cargo-test` blocking every PR (#6957). diff --git a/crates/perry-runtime/src/gc/layout.rs b/crates/perry-runtime/src/gc/layout.rs index 63ab824fec..8922e48e91 100644 --- a/crates/perry-runtime/src/gc/layout.rs +++ b/crates/perry-runtime/src/gc/layout.rs @@ -351,10 +351,16 @@ unsafe fn object_keys_array_ptr(user_ptr: usize) -> usize { (*(user_ptr as *const crate::object::ObjectHeader)).keys_array as usize } -/// The shared canonical descriptor for `user_ptr`'s shape, if shape-keying is -/// on, the object carries a keys_array, and the shape is unambiguous (`Some`). +/// Borrow the shared canonical descriptor for `user_ptr`'s shape, if +/// shape-keying is on, the object carries a keys_array, and the shape is +/// unambiguous (`Some`). Runs `f` against the descriptor in place — the GC +/// trace path and the store fast path both consult it per object/per store, and +/// a `Heap` mask would allocate a `Vec` on every clone. #[inline] -unsafe fn shape_shared_descriptor(user_ptr: usize) -> Option { +unsafe fn with_shape_shared_descriptor( + user_ptr: usize, + f: impl Fn(&TypedLayoutDescriptor) -> R, +) -> Option { if !shape_layout_keyed_enabled() { return None; } @@ -362,7 +368,6 @@ unsafe fn shape_shared_descriptor(user_ptr: usize) -> Option Option Option { + with_shape_shared_descriptor(user_ptr, |desc| desc.clone()) +} + +/// Answer a *query* about `user_ptr`'s current canonical typed layout, whichever +/// map holds it: the per-object `TYPED_LAYOUTS` entry (objects that diverged +/// from their shape, or carry no keys_array), else — and only while the object +/// is still `GC_OBJ_TYPED_LAYOUT_INTACT` — the shape-shared `SHAPE_LAYOUTS` +/// entry. +/// +/// #6957: #6893 moved the descriptor of every *shape-keyed* object (i.e. every +/// class instance — it carries a shared `keys_array`) out of `TYPED_LAYOUTS` and +/// **deleted the per-object entry**. It taught `layout_note_slot`, +/// `layout_visit_pointer_slots` and `heap_payload_slot_selection`'s mask lookup +/// about the new home but not the query helpers below, so every one of them +/// started reporting "no typed descriptor" for real class instances — silently +/// deopting every typed guard that consults them. The existing layout tests all +/// allocate with `js_object_alloc` (class 0, no keys_array), which still takes +/// the per-object path, so nothing caught it. +/// +/// The INTACT gate on the shared half is load-bearing. +/// `layout_set_typed_unknown` downgrades exactly ONE object (a store that +/// contradicts the descriptor) by clearing its intact bit and dropping its +/// per-object entry; it cannot drop the `SHAPE_LAYOUTS` entry, which still +/// correctly describes every sibling that has *not* diverged. Reading the shared +/// descriptor without the bit would therefore keep reporting the pre-downgrade +/// layout for the very object that just invalidated it. +/// +/// The per-object half stays ungated, so this remains an independent check on a +/// forged/stale intact header bit (see +/// [`layout_typed_accepts_finite_number_slot_for_user`]). +#[inline] +fn with_typed_descriptor_for_query( + user_ptr: usize, + f: impl Fn(&TypedLayoutDescriptor) -> R, +) -> Option { + if let Some(result) = TYPED_LAYOUTS.with(|m| m.borrow().get(&user_ptr).map(&f)) { + return Some(result); + } + if !layout_typed_intact_for_user(user_ptr) { return None; } - Some(desc) + unsafe { with_shape_shared_descriptor(user_ptr, f) } } /// Trace-path helper: pointer mask for a SIDE_MASK object with no per-object @@ -739,11 +796,22 @@ pub(crate) fn layout_slot_is_raw_f64_typed(parent_user: usize, slot_index: usize if (*header)._reserved & GC_OBJ_TYPED_LAYOUT_INTACT == 0 { return false; } - TYPED_LAYOUTS.with(|m| { - m.borrow().get(&parent_user).is_some_and(|typed| { - slot_index < typed.slot_count && typed.raw_f64_mask.contains_slot(slot_index) + // #6893/#6957: per-object descriptor (diverged objects, and objects with + // no keys_array) OR the shared shape descriptor — exactly as + // `layout_note_slot` resolves it, which is the agreement this helper + // documents. + TYPED_LAYOUTS + .with(|m| { + m.borrow().get(&parent_user).map(|typed| { + slot_index < typed.slot_count && typed.raw_f64_mask.contains_slot(slot_index) + }) + }) + .or_else(|| { + with_shape_shared_descriptor(parent_user, |typed| { + slot_index < typed.slot_count && typed.raw_f64_mask.contains_slot(slot_index) + }) }) - }) + .unwrap_or(false) } } @@ -1155,14 +1223,10 @@ pub(crate) fn layout_typed_intact_for_user(user_ptr: usize) -> bool { } pub(crate) fn layout_typed_raw_f64_slot_for_user(user_ptr: usize, slot_index: usize) -> bool { - TYPED_LAYOUTS.with(|m| { - m.borrow() - .get(&user_ptr) - .map(|layout| { - slot_index < layout.slot_count && layout.raw_f64_mask.contains_slot(slot_index) - }) - .unwrap_or(false) + with_typed_descriptor_for_query(user_ptr, |layout| { + slot_index < layout.slot_count && layout.raw_f64_mask.contains_slot(slot_index) }) + .unwrap_or(false) } /// Validate that an intact typed descriptor contains `slot_index`. @@ -1177,23 +1241,16 @@ pub(crate) fn layout_typed_accepts_finite_number_slot_for_user( user_ptr: usize, slot_index: usize, ) -> bool { - TYPED_LAYOUTS.with(|m| { - m.borrow() - .get(&user_ptr) - .is_some_and(|layout| slot_index < layout.slot_count) - }) + with_typed_descriptor_for_query(user_ptr, |layout| slot_index < layout.slot_count) + .unwrap_or(false) } fn layout_typed_raw_f64_slot_count_for_user(user_ptr: usize, slot_count: usize) -> usize { - TYPED_LAYOUTS.with(|m| { - m.borrow() - .get(&user_ptr) - .map(|layout| { - let bounded_count = slot_count.min(layout.slot_count); - layout.raw_f64_mask.count_slots(bounded_count) - }) - .unwrap_or(0) + with_typed_descriptor_for_query(user_ptr, |layout| { + let bounded_count = slot_count.min(layout.slot_count); + layout.raw_f64_mask.count_slots(bounded_count) }) + .unwrap_or(0) } #[derive(Clone, Copy, Debug, Eq, PartialEq)] diff --git a/crates/perry-runtime/src/gc/tests/layout_trace.rs b/crates/perry-runtime/src/gc/tests/layout_trace.rs index b2cce8a815..e568d067ba 100644 --- a/crates/perry-runtime/src/gc/tests/layout_trace.rs +++ b/crates/perry-runtime/src/gc/tests/layout_trace.rs @@ -464,6 +464,98 @@ fn test_typed_shape_descriptor_tracks_raw_numeric_slots() { clear_mark_seeds(); } +/// #6957 regression guard: the typed descriptor of a **shape-keyed** object must +/// be visible to the layout query helpers. +/// +/// #6893 keys the canonical descriptor by the shared `keys_array` (`SHAPE_LAYOUTS`) +/// and deletes the per-object `TYPED_LAYOUTS` entry — so every class instance +/// (the only objects that carry a keys_array) moved to the shared map. Every +/// other test in this file allocates with `js_object_alloc` (class 0, no +/// keys_array), which still takes the per-object path; that is precisely why the +/// query helpers could go blind on real class instances with the whole layout +/// suite green. +#[test] +fn test_typed_shape_descriptor_visible_for_shape_keyed_objects() { + clear_marks(); + clear_mark_seeds(); + + let packed = b"x\0y\0"; + let keys = crate::object::js_build_class_keys_array( + 0x6957_01, + 2, + packed.as_ptr(), + packed.len() as u32, + ); + let first = crate::object::js_object_alloc_class_inline_keys(0x6957_01, 0, 2, keys); + let second = crate::object::js_object_alloc_class_inline_keys(0x6957_01, 0, 2, keys); + unsafe { + assert_eq!( + (*first).keys_array, + (*second).keys_array, + "same-shape objects must share one canonical keys array" + ); + } + + let raw_mask = [0b01u64]; + for object in [first, second] { + crate::object::js_object_set_unboxed_f64_field(object, 0, 1.5); + crate::object::js_object_set_field(object, 1, crate::value::JSValue::number(2.5)); + js_gc_init_typed_shape_layout( + object as u64, + 2, + raw_mask.as_ptr(), + raw_mask.len() as u32, + std::ptr::null(), + 0, + ); + } + + for object in [first, second] { + let user = object as usize; + assert!( + layout_typed_intact_for_user(user), + "the shared shape install must set the intact bit" + ); + assert!( + layout_typed_raw_f64_slot_for_user(user, 0), + "slot 0 is raw-f64 in the shape descriptor" + ); + assert!(!layout_typed_raw_f64_slot_for_user(user, 1)); + assert!( + layout_slot_is_raw_f64_typed(user, 0), + "the store fast path must agree with layout_note_slot's own resolution" + ); + assert!( + layout_typed_accepts_finite_number_slot_for_user(user, 1), + "an ordinary JSValue slot of an intact descriptor accepts finite numbers" + ); + } + + // A contradicting store downgrades ONLY the object that made it. The shared + // entry cannot be removed (it still describes every sibling), so the intact + // bit is what separates the two — assert both halves. + let payload = crate::string::js_string_from_bytes(b"boxed".as_ptr(), 5); + crate::object::js_object_set_field(first, 0, crate::value::JSValue::string_ptr(payload)); + + assert!( + !layout_typed_raw_f64_slot_for_user(first as usize, 0), + "a boxed store into a raw-f64 slot must evict this object's descriptor" + ); + assert!(!layout_slot_is_raw_f64_typed(first as usize, 0)); + assert!( + !layout_typed_accepts_finite_number_slot_for_user(first as usize, 0), + "a downgraded object must not keep reading its shape's stale descriptor" + ); + assert!( + layout_typed_raw_f64_slot_for_user(second as usize, 0), + "the sibling never diverged and must keep the shared shape descriptor" + ); + assert!(layout_slot_is_raw_f64_typed(second as usize, 0)); + + clear_marks(); + clear_mark_seeds(); +} + #[test] fn test_typed_shape_raw_numeric_slots_accept_pointer_like_f64_bits() { clear_marks(); diff --git a/crates/perry-runtime/src/typed_feedback.rs b/crates/perry-runtime/src/typed_feedback.rs index bc6d499935..eadb460c23 100644 --- a/crates/perry-runtime/src/typed_feedback.rs +++ b/crates/perry-runtime/src/typed_feedback.rs @@ -51,7 +51,29 @@ pub(crate) fn typed_feedback_active() -> bool { } #[cfg(test)] -pub(crate) static TYPED_FEEDBACK_TEST_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); +static TYPED_FEEDBACK_TEST_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); + +/// Serializes the typed-feedback unit tests, which all drive the one +/// process-global site registry. +/// +/// Recovers from poisoning **on purpose**. The mutex guards no invariant of its +/// own: every test re-initializes the shared state with +/// `reset_typed_feedback_for_tests()` as its next statement, so a lock left +/// poisoned by an earlier test's assertion failure is still perfectly usable. +/// +/// #6957: with a plain `.unwrap()`, the first genuine failure in the module +/// poisoned the lock and turned all 31 subsequent tests into `PoisonError` +/// panics. That reported two real regressions as 33 red tests, hid which two +/// were real, and made the module's result depend on `--test-threads` (32 red +/// serially, 33 in parallel — purely a function of how many tests ran *after* +/// the poisoning one). Recovering keeps a failure count equal to the number of +/// actual failures. +#[cfg(test)] +pub(crate) fn typed_feedback_test_lock() -> std::sync::MutexGuard<'static, ()> { + TYPED_FEEDBACK_TEST_LOCK + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) +} #[repr(u32)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] diff --git a/crates/perry-runtime/src/typed_feedback/tests.rs b/crates/perry-runtime/src/typed_feedback/tests.rs index 5892582201..dee670913c 100644 --- a/crates/perry-runtime/src/typed_feedback/tests.rs +++ b/crates/perry-runtime/src/typed_feedback/tests.rs @@ -155,7 +155,7 @@ fn unique_temp_dir(name: &str) -> std::path::PathBuf { #[test] fn typed_feedback_registers_source_attribution() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(1, TypedFeedbackSiteKind::PropertyGet, "obj.x"); let snapshot = typed_feedback_snapshot(); @@ -169,7 +169,7 @@ fn typed_feedback_registers_source_attribution() { #[test] fn typed_feedback_state_transitions_to_megamorphic() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(2, TypedFeedbackSiteKind::HelperReturn, "helper"); for i in 0..POLYMORPHIC_CAP { @@ -208,7 +208,7 @@ fn typed_feedback_state_transitions_to_megamorphic() { #[test] fn typed_feedback_invalidation_counters_are_site_attributed() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(3, TypedFeedbackSiteKind::MethodCall, "m"); observe( @@ -233,7 +233,7 @@ fn typed_feedback_invalidation_counters_are_site_attributed() { #[test] fn typed_feedback_property_and_method_keys_ignore_receiver_identity() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(5, TypedFeedbackSiteKind::PropertyGet, "obj.x"); register(6, TypedFeedbackSiteKind::MethodCall, "obj.m()"); @@ -278,7 +278,7 @@ fn typed_feedback_property_and_method_keys_ignore_receiver_identity() { #[test] fn typed_feedback_array_keys_use_element_facts_not_sample_identity() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(7, TypedFeedbackSiteKind::ArrayElement, "arr[i]"); @@ -304,7 +304,7 @@ fn typed_feedback_array_keys_use_element_facts_not_sample_identity() { #[test] fn typed_feedback_helper_return_keys_use_shape_facts_not_sample_identity() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(8, TypedFeedbackSiteKind::HelperReturn, "helper()"); @@ -339,7 +339,7 @@ fn typed_feedback_helper_return_keys_use_shape_facts_not_sample_identity() { #[test] fn typed_feedback_tracks_all_site_categories() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); let kinds = [ TypedFeedbackSiteKind::PropertyGet, @@ -363,7 +363,7 @@ fn typed_feedback_tracks_all_site_categories() { #[test] fn typed_feedback_unboxed_numeric_write_falls_back_for_string_values() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(21, TypedFeedbackSiteKind::NumericFieldWrite, "obj.x="); @@ -392,7 +392,7 @@ fn typed_feedback_unboxed_numeric_write_falls_back_for_string_values() { #[test] fn typed_feedback_helper_return_guard_failure_returns_original_value() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(22, TypedFeedbackSiteKind::HelperReturn, "helper()"); @@ -412,7 +412,7 @@ fn typed_feedback_helper_return_guard_failure_returns_original_value() { #[test] fn typed_feedback_array_guard_failure_matches_jsvalue_fallback() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(23, TypedFeedbackSiteKind::ArrayElement, "arr[i]"); @@ -430,7 +430,7 @@ fn typed_feedback_array_guard_failure_matches_jsvalue_fallback() { #[test] fn typed_feedback_array_get_guard_failure_uses_jsvalue_object_fallback() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(25, TypedFeedbackSiteKind::ArrayElement, "arr[i]"); @@ -456,7 +456,7 @@ fn typed_feedback_array_get_guard_failure_uses_jsvalue_object_fallback() { #[test] fn typed_feedback_non_bounded_array_set_guard_failure_uses_jsvalue_object_fallback() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(24, TypedFeedbackSiteKind::ArrayElement, "arr[i]="); @@ -484,7 +484,7 @@ fn typed_feedback_non_bounded_array_set_guard_failure_uses_jsvalue_object_fallba #[test] fn typed_feedback_array_set_guards_reject_frozen_arrays() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(70, TypedFeedbackSiteKind::ArrayElement, "arr[i]="); register(71, TypedFeedbackSiteKind::ArrayElement, "arr[i]="); @@ -517,7 +517,7 @@ fn typed_feedback_array_set_guards_reject_frozen_arrays() { #[test] fn typed_feedback_array_set_boxed_fallback_preserves_original_index_value() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(72, TypedFeedbackSiteKind::ArrayElement, "arr[i]="); @@ -542,7 +542,7 @@ fn typed_feedback_array_set_boxed_fallback_preserves_original_index_value() { #[test] fn typed_feedback_boxed_fallback_preserves_fractional_keys_for_array_like_receivers() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(73, TypedFeedbackSiteKind::ArrayElement, "arr[i]"); @@ -602,7 +602,7 @@ fn typed_feedback_boxed_fallback_preserves_fractional_keys_for_array_like_receiv #[test] fn typed_feedback_boxed_set_fallback_does_not_truncate_fractional_array_like_keys() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(74, TypedFeedbackSiteKind::ArrayElement, "arr[i]="); @@ -752,7 +752,7 @@ fn polymorphic_index_fallbacks_preserve_fractional_keys_for_array_like_receivers #[test] fn typed_feedback_numeric_array_get_guard_requires_numeric_layout() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(26, TypedFeedbackSiteKind::ArrayElement, "arr[i]"); @@ -779,7 +779,7 @@ fn typed_feedback_numeric_array_get_guard_requires_numeric_layout() { #[test] fn typed_feedback_packed_i32_loop_guard_rejects_fractional_numeric_layout() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(70, TypedFeedbackSiteKind::ArrayElement, "packed_i32_loop"); @@ -811,7 +811,7 @@ fn typed_feedback_packed_i32_loop_guard_rejects_fractional_numeric_layout() { #[test] fn typed_feedback_packed_u32_loop_guard_rejects_signed_fractional_and_overflow_layouts() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(71, TypedFeedbackSiteKind::ArrayElement, "packed_u32_loop"); @@ -840,7 +840,7 @@ fn typed_feedback_packed_u32_loop_guard_rejects_signed_fractional_and_overflow_l #[test] fn typed_feedback_numeric_array_set_guard_requires_numeric_value_and_layout() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(27, TypedFeedbackSiteKind::ArrayElement, "arr[i]="); @@ -869,7 +869,7 @@ fn typed_feedback_numeric_array_set_guard_requires_numeric_value_and_layout() { #[test] fn typed_feedback_numeric_array_guards_reject_registered_class_ref_bits() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(68, TypedFeedbackSiteKind::ArrayElement, "arr[i]="); register(69, TypedFeedbackSiteKind::ArrayElement, "arr.push"); @@ -913,7 +913,7 @@ fn typed_feedback_numeric_array_guards_reject_registered_class_ref_bits() { #[test] fn typed_feedback_numeric_array_push_guard_requires_room_numeric_value_and_layout() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(28, TypedFeedbackSiteKind::ArrayElement, "arr.push"); @@ -943,7 +943,7 @@ fn typed_feedback_numeric_array_push_guard_requires_room_numeric_value_and_layou #[test] fn typed_feedback_numeric_array_push_guard_rejects_mutability_restricted_arrays() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(72, TypedFeedbackSiteKind::ArrayElement, "arr.push"); @@ -1538,7 +1538,7 @@ fn representation_lowering_helpers_have_lto_keepalive_anchors() { #[test] fn typed_feedback_class_field_set_guard_fails_for_frozen_object() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(31, TypedFeedbackSiteKind::PropertySet, "obj.x="); @@ -1563,7 +1563,7 @@ fn typed_feedback_class_field_set_guard_fails_for_frozen_object() { #[test] fn typed_feedback_class_field_set_guard_falls_back_for_class_setter() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); CLASS_FIELD_SETTER_CALLS.store(0, std::sync::atomic::Ordering::SeqCst); CLASS_FIELD_SETTER_VALUE_BITS.store(0, std::sync::atomic::Ordering::SeqCst); @@ -1608,7 +1608,7 @@ fn typed_feedback_class_field_set_guard_falls_back_for_class_setter() { #[test] fn typed_feedback_class_field_get_guard_falls_back_after_shape_transition() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(39, TypedFeedbackSiteKind::PropertyGet, "obj.x"); @@ -1638,7 +1638,7 @@ fn typed_feedback_class_field_get_guard_falls_back_after_shape_transition() { #[test] fn typed_feedback_class_field_get_guard_requires_raw_f64_layout_when_requested() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(43, TypedFeedbackSiteKind::PropertyGet, "obj.x"); @@ -1674,7 +1674,7 @@ fn typed_feedback_class_field_get_guard_requires_raw_f64_layout_when_requested() #[test] fn typed_feedback_class_field_set_guard_requires_raw_f64_value_and_layout() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(44, TypedFeedbackSiteKind::PropertySet, "obj.x="); @@ -1750,7 +1750,7 @@ fn typed_feedback_class_field_set_guard_requires_raw_f64_value_and_layout() { #[test] fn typed_feedback_object_set_fast_hits_learned_dynamic_key_transition() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(34, TypedFeedbackSiteKind::PropertySet, "obj[dyn]="); @@ -1783,7 +1783,7 @@ fn typed_feedback_object_set_fast_hits_learned_dynamic_key_transition() { #[test] fn typed_feedback_object_set_fast_falls_back_for_uncached_dynamic_key() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(35, TypedFeedbackSiteKind::PropertySet, "obj[dyn_miss]="); @@ -1800,7 +1800,7 @@ fn typed_feedback_object_set_fast_falls_back_for_uncached_dynamic_key() { #[test] fn typed_feedback_method_direct_guard_passes_for_exact_registered_method() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(61, TypedFeedbackSiteKind::MethodCall, "obj.m()"); @@ -1830,7 +1830,7 @@ fn typed_feedback_method_direct_guard_passes_for_exact_registered_method() { #[test] fn typed_feedback_method_direct_guard_fails_for_own_method_replacement() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(62, TypedFeedbackSiteKind::MethodCall, "obj.m()"); @@ -1862,7 +1862,7 @@ fn typed_feedback_method_direct_guard_fails_for_own_method_replacement() { #[test] fn typed_feedback_method_direct_guard_fails_for_prototype_method_registration() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(63, TypedFeedbackSiteKind::MethodCall, "obj.m()"); @@ -1900,7 +1900,7 @@ fn typed_feedback_method_direct_guard_fails_for_prototype_method_registration() #[test] fn typed_feedback_method_direct_guard_fails_for_native_receiver() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(64, TypedFeedbackSiteKind::MethodCall, "native.m()"); @@ -1928,7 +1928,7 @@ fn typed_feedback_method_direct_guard_fails_for_native_receiver() { #[test] fn typed_feedback_method_direct_guard_fails_after_megamorphic_site() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(65, TypedFeedbackSiteKind::MethodCall, "obj.m()"); for i in 0..=POLYMORPHIC_CAP { @@ -1971,7 +1971,7 @@ fn typed_feedback_method_direct_guard_fails_after_megamorphic_site() { #[test] fn typed_feedback_closure_direct_guard_passes_and_rejects_bound_sentinel() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(66, TypedFeedbackSiteKind::ClosureCall, "cb()"); @@ -1994,7 +1994,7 @@ fn typed_feedback_closure_direct_guard_passes_and_rejects_bound_sentinel() { #[test] fn typed_feedback_trace_json_reports_counts() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(4, TypedFeedbackSiteKind::ArrayElement, "arr[i]"); js_typed_feedback_record_guard_pass(4); @@ -2023,7 +2023,7 @@ fn typed_feedback_trace_json_reports_counts() { #[test] fn typed_feedback_trace_json_includes_observed_kinds() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(67, TypedFeedbackSiteKind::ArrayElement, "arr[i]="); @@ -2071,7 +2071,7 @@ fn typed_feedback_trace_json_includes_observed_kinds() { #[test] fn typed_feedback_trace_dump_honors_env_paths() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); let disabled_dir = unique_temp_dir("disabled"); @@ -2114,7 +2114,7 @@ fn typed_feedback_trace_dump_honors_env_paths() { #[test] fn typed_feedback_roots_rewrite_shape_observations() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); let shape_user = crate::arena::arena_alloc_gc(64, 8, crate::gc::GC_TYPE_ARRAY); @@ -2280,7 +2280,7 @@ fn typed_array_alloc_always_carries_a_real_typed_array_gc_header() { /// rather than the inline plain-`ArrayHeader` raw-slot path. #[test] fn plain_array_index_get_guard_rejects_typed_array_receivers() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(6136, TypedFeedbackSiteKind::ArrayElement, "nd.buf[i]"); @@ -2311,7 +2311,7 @@ fn plain_array_index_get_guard_rejects_typed_array_receivers() { /// above proves rejection of typed arrays rather than a guard that never passes. #[test] fn plain_array_index_get_guard_still_accepts_plain_arrays() { - let _guard = TYPED_FEEDBACK_TEST_LOCK.lock().unwrap(); + let _guard = typed_feedback_test_lock(); reset_typed_feedback_for_tests(); register(6137, TypedFeedbackSiteKind::ArrayElement, "arr[i]");