diff --git a/changelog.d/7485-repsel-4b-field-store-elision.md b/changelog.d/7485-repsel-4b-field-store-elision.md new file mode 100644 index 0000000000..b88d7eface --- /dev/null +++ b/changelog.d/7485-repsel-4b-field-store-elision.md @@ -0,0 +1 @@ +**Repsel Phase 4b cleanup: dead `PERRY_UNBOXED_OBJECT_FIELDS` prototype deleted.** The Phase 4b (narrow) mandatory items — class-field store note/addref elision on `Ptr`-proven receivers (4b.1) and the INT32 layout-poison canonicalization (4b.2) — landed in #6919/#6930; this ships the remaining sanctioned item. The prototype's write path was bit-identical to the default typed-shape path and its read side was never implemented, so the env flag, the hard-coded `{x,y}` object-literal matcher, `js_gc_init_unboxed_object_layout`, `js_object_{get,set}_unboxed_f64_field`, the codegen declarations/`gc_call_effects` entry, and the flag's object-cache/build-cache key entries are gone. The `js_typed_feedback_object_set_unboxed_f64_field` sentinel symbol stays (`check_runtime_symbols.sh` + keepalive-anchor surface, #854 foundation); its guarded fast path now uses the plain indexed setter, which routes through the identical `runtime_store_jsvalue_slot`. GC layout tests that used the prototype installer as a store convenience moved onto the load-bearing `js_gc_init_typed_shape_layout` (`layout_trace/unboxed_object.rs` → `layout_trace/object_layout_invalidation.rs`), preserving the shape-change invalidation coverage (dynamic add / delete / `defineProperty` / accessors) on the default path; prototype-only twins of existing typed-shape tests were dropped. Full gap suite: zero changed outcomes vs a same-session pristine-main baseline (measured twice, before and after rebasing onto #7474's main). The `addr_class` ratchet baseline tightened by the deleted setter's bare-address site. diff --git a/crates/perry-codegen/src/expr/object_literal.rs b/crates/perry-codegen/src/expr/object_literal.rs index 5aaf35d26a..43e61327d1 100644 --- a/crates/perry-codegen/src/expr/object_literal.rs +++ b/crates/perry-codegen/src/expr/object_literal.rs @@ -11,7 +11,7 @@ use super::temp_root::{ }; use super::{lower_expr, nanbox_pointer_inline, FnCtx}; use crate::nanbox::POINTER_MASK_I64; -use crate::type_analysis::{compute_auto_captures, is_numeric_expr}; +use crate::type_analysis::compute_auto_captures; use crate::types::{DOUBLE, I32, I64, PTR}; fn expected_interface_property_type( @@ -120,87 +120,6 @@ fn typed_object_literal_layout( }) } -fn unboxed_object_fields_enabled() -> bool { - matches!( - std::env::var("PERRY_UNBOXED_OBJECT_FIELDS").as_deref(), - Ok("1") - ) -} - -fn is_number_type(ty: &HirType) -> bool { - matches!(ty, HirType::Number) -} - -fn object_type_is_exact_xy_number(ty: &perry_hir::types::ObjectType) -> bool { - ty.index_signature.is_none() - && ty.properties.len() == 2 - && ty - .properties - .get("x") - .map(|prop| is_number_type(&prop.ty)) - .unwrap_or(false) - && ty - .properties - .get("y") - .map(|prop| is_number_type(&prop.ty)) - .unwrap_or(false) -} - -fn interface_is_exact_xy_number(iface: &perry_hir::Interface) -> bool { - iface.extends.is_empty() - && iface.methods.is_empty() - && iface.properties.len() == 2 - && iface - .properties - .iter() - .any(|prop| prop.name == "x" && is_number_type(&prop.ty)) - && iface - .properties - .iter() - .any(|prop| prop.name == "y" && is_number_type(&prop.ty)) -} - -fn expected_type_is_exact_xy_number(ctx: &FnCtx<'_>, expected_ty: &HirType, depth: usize) -> bool { - if depth > 32 { - return false; - } - match expected_ty { - HirType::Object(obj) => object_type_is_exact_xy_number(obj), - HirType::Named(name) => { - if let Some(alias) = ctx.type_aliases.get(name) { - if expected_type_is_exact_xy_number(ctx, alias, depth + 1) { - return true; - } - } - ctx.interfaces - .get(name) - .map(interface_is_exact_xy_number) - .unwrap_or(false) - } - _ => false, - } -} - -fn unboxed_xy_object_literal( - ctx: &FnCtx<'_>, - props: &[(String, Expr)], - expected_ty: Option<&HirType>, -) -> bool { - if !unboxed_object_fields_enabled() { - return false; - } - if props.len() != 2 || props[0].0 != "x" || props[1].0 != "y" { - return false; - } - let Some(expected_ty) = expected_ty else { - return false; - }; - expected_type_is_exact_xy_number(ctx, expected_ty, 0) - && props - .iter() - .all(|(_, value_expr)| is_numeric_expr(ctx, value_expr)) -} - fn emit_object_mask_global(ctx: &mut FnCtx<'_>, kind: &str, mask_words: &[u64]) -> String { if mask_words.is_empty() { return "null".to_string(); @@ -253,13 +172,6 @@ fn emit_object_typed_shape_init( ); } -fn emit_unboxed_object_layout_init(ctx: &mut FnCtx<'_>, obj_handle: &str) { - ctx.block().call_void( - "js_gc_init_unboxed_object_layout", - &[(I64, obj_handle), (I32, "2"), (I64, "3"), (I64, "0")], - ); -} - fn is_generator_iterator_object_literal(props: &[(String, Expr)]) -> bool { if props.len() != 3 { return false; @@ -343,55 +255,6 @@ pub(crate) fn lower_object_literal( ) }); - if !any_method_closure && unboxed_xy_object_literal(ctx, props, expected_ty) { - let mut packed_keys = String::new(); - for (k, _) in props { - packed_keys.push_str(k); - packed_keys.push('\0'); - } - let keys_idx = ctx.strings.intern(&packed_keys); - let keys_entry = ctx.strings.entry(keys_idx); - let keys_global = format!("@{}", keys_entry.bytes_global); - let keys_len_str = keys_entry.byte_len.to_string(); - - let mut shape_id: u32 = 0x811c9dc5; - for b in packed_keys.as_bytes() { - shape_id ^= *b as u32; - shape_id = shape_id.wrapping_mul(0x01000193); - } - if shape_id == 0 { - shape_id = 1; - } - let shape_id_str = shape_id.to_string(); - - let obj_handle = ctx.block().call( - I64, - "js_object_alloc_with_shape", - &[ - (I32, &shape_id_str), - (I32, &n_str), - (PTR, &keys_global), - (I32, &keys_len_str), - ], - ); - - let rooted = rooted_handle_begin(ctx, &obj_handle, protect_handle); - for (i, (_, value_expr)) in props.iter().enumerate() { - let v = lower_expr(ctx, value_expr)?; - let idx_str = i.to_string(); - let obj_handle = rooted_handle_get(ctx, &rooted); - ctx.block().call_void( - "js_object_set_unboxed_f64_field", - &[(I64, &obj_handle), (I32, &idx_str), (DOUBLE, &v)], - ); - } - let obj_handle = rooted_handle_get(ctx, &rooted); - emit_unboxed_object_layout_init(ctx, &obj_handle); - let boxed = nanbox_pointer_inline(ctx.block(), &obj_handle); - rooted_handle_release(ctx, rooted); - return Ok(boxed); - } - if !any_method_closure && field_count > 0 { // Build packed keys "k1\0k2\0…" interned in the StringPool (shared // across all literals with the same key set + order). diff --git a/crates/perry-codegen/src/gc_call_effects.rs b/crates/perry-codegen/src/gc_call_effects.rs index c4474eb66a..78fd678d4b 100644 --- a/crates/perry-codegen/src/gc_call_effects.rs +++ b/crates/perry-codegen/src/gc_call_effects.rs @@ -82,7 +82,6 @@ pub(crate) fn classify_direct_callee(name: &str) -> GcCallEffect { | "js_gc_note_slot_layout" | "js_gc_note_slot_layout_aware" | "js_gc_init_typed_shape_layout" - | "js_gc_init_unboxed_object_layout" // `typed_feedback.rs`: counters/registries only. This intentionally // does not include feedback wrappers that perform the actual object // get/set operation. diff --git a/crates/perry-codegen/src/runtime_decls/arrays.rs b/crates/perry-codegen/src/runtime_decls/arrays.rs index b1e78828cd..52c3d38ce0 100644 --- a/crates/perry-codegen/src/runtime_decls/arrays.rs +++ b/crates/perry-codegen/src/runtime_decls/arrays.rs @@ -142,7 +142,6 @@ pub fn declare_phase_b_arrays(module: &mut LlModule) { // js_write_barrier_root_heap_word(child_bits: u64) // js_gc_note_slot_layout(parent_bits: u64, slot_index: u32, value_bits: u64) // js_gc_init_typed_shape_layout(obj: u64, slot_count: u32, raw_f64_mask_words: *const u64, raw_f64_mask_word_count: u32, pointer_mask_words: *const u64, pointer_mask_word_count: u32) - // js_gc_init_unboxed_object_layout(obj: u64, slot_count: u32, raw_f64_mask: u64, pointer_mask: u64) module.declare_function("js_write_barrier", VOID, &[I64, I64]); module.declare_function("js_write_barrier_slot", VOID, &[I64, I64, I64]); module.declare_function("js_write_barrier_root_nanbox", VOID, &[I64]); @@ -155,12 +154,6 @@ pub fn declare_phase_b_arrays(module: &mut LlModule) { VOID, &[I64, I32, PTR, I32, PTR, I32], ); - module.declare_function( - "js_gc_init_unboxed_object_layout", - VOID, - &[I64, I32, I64, I64], - ); - // Array methods (Phase B.12). // - js_array_pop_f64(arr) -> f64 (last element, NaN if empty) // - js_array_join(arr, sep) -> *mut StringHeader (i64) diff --git a/crates/perry-codegen/src/runtime_decls/objects.rs b/crates/perry-codegen/src/runtime_decls/objects.rs index 36a0fa1546..d7cb0557af 100644 --- a/crates/perry-codegen/src/runtime_decls/objects.rs +++ b/crates/perry-codegen/src/runtime_decls/objects.rs @@ -87,8 +87,6 @@ pub fn declare_phase_b_objects(module: &mut LlModule) { // loop hung forever. Declaring the slot as I64 routes through the // same register class the runtime actually reads. module.declare_function("js_object_set_field", VOID, &[I64, I32, I64]); - module.declare_function("js_object_set_unboxed_f64_field", VOID, &[I64, I32, DOUBLE]); - module.declare_function("js_object_get_unboxed_f64_field", DOUBLE, &[I64, I32]); module.declare_function("js_object_set_field_by_name", VOID, &[I64, I64, DOUBLE]); module.declare_function( "js_object_set_field_by_property_id", diff --git a/crates/perry-codegen/tests/typed_shape_descriptors.rs b/crates/perry-codegen/tests/typed_shape_descriptors.rs index d154d5317c..598cd51afe 100644 --- a/crates/perry-codegen/tests/typed_shape_descriptors.rs +++ b/crates/perry-codegen/tests/typed_shape_descriptors.rs @@ -5,33 +5,6 @@ use perry_hir::{ ModuleInitKind, Stmt, UpdateOp, }; -static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); - -struct EnvVarGuard { - key: &'static str, - prev: Option, -} - -impl EnvVarGuard { - fn set(key: &'static str, value: Option<&str>) -> Self { - let prev = std::env::var_os(key); - match value { - Some(value) => std::env::set_var(key, value), - None => std::env::remove_var(key), - } - Self { key, prev } - } -} - -impl Drop for EnvVarGuard { - fn drop(&mut self) { - match &self.prev { - Some(value) => std::env::set_var(self.key, value), - None => std::env::remove_var(self.key), - } - } -} - fn empty_opts() -> CompileOptions { CompileOptions { target: None, @@ -331,10 +304,6 @@ fn assert_typed_feedback_setter_after(ir: &str, start_pos: usize, context: &str) ir.contains("js_object_set_field_by_name"), "{context} should keep the safe runtime setter as the typed-feedback fallback" ); - assert!( - !after_start.contains("call void @js_object_set_unboxed_f64_field"), - "{context} should not use the raw unboxed field setter for dynamic mutation" - ); } fn point_module(name: &str, body: Vec) -> Module { @@ -936,46 +905,13 @@ fn typed_object_literal_pointer_free_descriptor_precedes_dynamic_mutation() { ); } +// The `PERRY_UNBOXED_OBJECT_FIELDS` prototype was deleted (Phase 4b cleanup): +// its write path was bit-identical to the default typed-shape path and its +// read side was never implemented. This test pins the default path the flag +// used to bypass: exact `{x, y}` number literals go through the shape-cache +// allocator, indexed setters, and a typed-shape descriptor install. #[test] -fn unboxed_point_literal_gate_on_emits_raw_setters_and_pointer_free_layout() { - let _lock = ENV_LOCK.lock().unwrap(); - let _env = EnvVarGuard::set("PERRY_UNBOXED_OBJECT_FIELDS", Some("1")); - let point_ty = object_type(&[("x", Type::Number), ("y", Type::Number)]); - let module = point_module( - "unboxed_point_on.ts", - vec![ - Stmt::Let { - id: 1, - name: "p".to_string(), - ty: point_ty, - mutable: false, - init: Some(Expr::Object(vec![ - ("x".to_string(), Expr::Number(1.5)), - ("y".to_string(), Expr::Number(2.5)), - ])), - }, - Stmt::Return(Some(Expr::LocalGet(1))), - ], - ); - - let ir = ir_for(module); - assert!(ir.contains("call i64 @js_object_alloc_with_shape")); - assert!(ir.contains("call void @js_object_set_unboxed_f64_field")); - assert!(ir.contains("call void @js_gc_init_unboxed_object_layout")); - assert!( - ir.contains("i32 2, i64 3, i64 0"), - "unboxed point layout should install raw f64 slots for x/y and no pointer slots" - ); - assert!( - !ir.contains("call void @js_gc_init_typed_shape_layout"), - "gate-on exact point literals should use the unboxed layout installer" - ); -} - -#[test] -fn unboxed_point_literal_gate_off_uses_existing_typed_shape_path() { - let _lock = ENV_LOCK.lock().unwrap(); - let _env = EnvVarGuard::set("PERRY_UNBOXED_OBJECT_FIELDS", None); +fn point_literal_uses_typed_shape_path() { let point_ty = object_type(&[("x", Type::Number), ("y", Type::Number)]); let module = point_module( "unboxed_point_off.ts", @@ -1000,44 +936,4 @@ fn unboxed_point_literal_gate_off_uses_existing_typed_shape_path() { assert!(ir.contains("call void @js_gc_init_typed_shape_layout")); assert!(ir.contains("@perry_typed_obj_shape_raw_f64_mask_")); assert!(ir.contains("constant [1 x i64] [i64 3]")); - assert!(!ir.contains("call void @js_object_set_unboxed_f64_field")); - assert!(!ir.contains("call void @js_gc_init_unboxed_object_layout")); -} - -#[test] -fn unboxed_point_dynamic_mutation_still_uses_safe_by_name_setter() { - let _lock = ENV_LOCK.lock().unwrap(); - let _env = EnvVarGuard::set("PERRY_UNBOXED_OBJECT_FIELDS", Some("1")); - let point_ty = object_type(&[("x", Type::Number), ("y", Type::Number)]); - let module = point_module( - "unboxed_point_mutation.ts", - vec![ - Stmt::Let { - id: 1, - name: "p".to_string(), - ty: point_ty, - mutable: true, - init: Some(Expr::Object(vec![ - ("x".to_string(), Expr::Number(1.0)), - ("y".to_string(), Expr::Number(2.0)), - ])), - }, - Stmt::Expr(Expr::PropertySet { - object: Box::new(Expr::LocalGet(1)), - property: "x".to_string(), - value: Box::new(Expr::String("heap".to_string())), - }), - Stmt::Return(Some(Expr::LocalGet(1))), - ], - ); - - let ir = ir_for(module); - let layout_pos = ir - .find("call void @js_gc_init_unboxed_object_layout") - .expect("fixture should install unboxed layout"); - assert_typed_feedback_setter_after( - &ir, - layout_pos, - "dynamic property mutation after an unboxed layout", - ); } diff --git a/crates/perry-runtime/src/gc/layout.rs b/crates/perry-runtime/src/gc/layout.rs index e3f9132bab..397f07ddbc 100644 --- a/crates/perry-runtime/src/gc/layout.rs +++ b/crates/perry-runtime/src/gc/layout.rs @@ -988,97 +988,6 @@ pub extern "C" fn js_gc_init_typed_shape_layout( } } -#[no_mangle] -pub extern "C" fn js_gc_init_unboxed_object_layout( - obj: u64, - slot_count: u32, - raw_f64_mask: u64, - pointer_mask: u64, -) { - let user_ptr = strip_nanbox_user_ptr(obj); - let slot_count = slot_count as usize; - if user_ptr == 0 || slot_count > 64 { - return; - } - unsafe { - let Some(header) = layout_header_for_user(user_ptr) else { - return; - }; - if gc_type_layout_slot_kind((*header).obj_type) != GcLayoutSlotKind::ObjectFields { - return; - } - let obj_header = user_ptr as *const crate::object::ObjectHeader; - let object_slot_count = (*obj_header).field_count as usize; - if object_slot_count != slot_count { - layout_set_typed_unknown(header, user_ptr); - return; - } - - let raw_f64_mask = LayoutSlotMask::Inline(raw_f64_mask); - let pointer_mask = LayoutSlotMask::Inline(pointer_mask); - if raw_f64_mask.intersects(&pointer_mask, slot_count) { - layout_set_typed_unknown(header, user_ptr); - return; - } - - if slot_count != 0 { - let fields = (obj_header as *const u8) - .add(std::mem::size_of::()) - as *const u64; - for i in 0..slot_count { - let bits = *fields.add(i); - if raw_f64_mask.contains_slot(i) { - if !layout_raw_f64_bits(bits) { - layout_set_typed_unknown(header, user_ptr); - return; - } - continue; - } - if layout_pointer_bearing_bits(bits) && !pointer_mask.contains_slot(i) { - layout_set_typed_unknown(header, user_ptr); - return; - } - } - } - - let descriptor = TypedLayoutDescriptor { - slot_count, - raw_f64_mask, - pointer_mask: pointer_mask.clone(), - }; - // #6893: shared shape descriptor before per-object storage. - let keys = if shape_layout_keyed_enabled() { - object_keys_array_ptr(user_ptr) - } else { - 0 - }; - if keys != 0 && shape_install_shared(keys, header, &descriptor) { - TYPED_LAYOUTS.with(|m| { - m.borrow_mut().remove(&user_ptr); - }); - LAYOUT_SLOT_MASKS.with(|m| { - m.borrow_mut().remove(&user_ptr); - }); - return; - } - TYPED_LAYOUTS.with(|m| { - m.borrow_mut().insert(user_ptr, descriptor); - }); - header_set_typed_layout_intact(header); - if pointer_mask.is_empty() { - set_layout_state(header, GC_LAYOUT_POINTER_FREE); - LAYOUT_SLOT_MASKS.with(|m| { - m.borrow_mut().remove(&user_ptr); - }); - } else { - set_layout_state(header, GC_LAYOUT_SIDE_MASK); - LAYOUT_SLOT_MASKS.with(|m| { - m.borrow_mut().insert(user_ptr, pointer_mask); - }); - } - } -} - pub(super) unsafe fn layout_rebuild_from_slots_with_policy( user_ptr: *mut u8, slots: *const u64, diff --git a/crates/perry-runtime/src/gc/tests/layout_trace.rs b/crates/perry-runtime/src/gc/tests/layout_trace.rs index 02d286c88f..cee6c1ced7 100644 --- a/crates/perry-runtime/src/gc/tests/layout_trace.rs +++ b/crates/perry-runtime/src/gc/tests/layout_trace.rs @@ -2,8 +2,8 @@ use super::super::*; use super::support::*; mod array_layout; mod object_closure_slots; +mod object_layout_invalidation; mod typed_shape; -mod unboxed_object; #[test] fn test_trace_array_marks_child() { diff --git a/crates/perry-runtime/src/gc/tests/layout_trace/unboxed_object.rs b/crates/perry-runtime/src/gc/tests/layout_trace/object_layout_invalidation.rs similarity index 68% rename from crates/perry-runtime/src/gc/tests/layout_trace/unboxed_object.rs rename to crates/perry-runtime/src/gc/tests/layout_trace/object_layout_invalidation.rs index 5ae281d95c..7eda7debef 100644 --- a/crates/perry-runtime/src/gc/tests/layout_trace/unboxed_object.rs +++ b/crates/perry-runtime/src/gc/tests/layout_trace/object_layout_invalidation.rs @@ -1,30 +1,16 @@ use super::*; -#[test] -fn test_unboxed_object_layout_scans_zero_raw_numeric_fields() { - clear_marks(); - clear_mark_seeds(); - - let obj = crate::object::js_object_alloc(0, 2); - crate::object::js_object_set_unboxed_f64_field(obj, 0, 1.25); - crate::object::js_object_set_unboxed_f64_field(obj, 1, -2.5); - js_gc_init_unboxed_object_layout(obj as u64, 2, 0b11, 0); - - assert_eq!(test_layout_pointer_slot_count(obj as usize, 2), Some(0)); - assert_eq!(test_heap_child_slot_count(obj as *mut u8), 0); - - let valid_ptrs = build_valid_pointer_set(); - assert!(try_mark_value( - POINTER_TAG | (obj as u64 & POINTER_MASK), - &valid_ptrs - )); - test_reset_trace_slot_reads(); - trace_marked_objects(&valid_ptrs); - assert_eq!(test_trace_slot_reads(), 0); - - clear_marks(); - clear_mark_seeds(); -} +// This file used to exercise the `PERRY_UNBOXED_OBJECT_FIELDS` prototype's +// `js_gc_init_unboxed_object_layout` installer. That prototype was deleted +// (Phase 4b cleanup): its write path was bit-identical to the default +// typed-shape path and its read side was never implemented. The coverage the +// file carried for behavior that still exists moved onto the load-bearing +// `js_gc_init_typed_shape_layout` installer: raw-numeric scan skipping, the +// copying-minor descriptor transfer, and descriptor invalidation on every +// shape-changing mutation (dynamic add, delete, defineProperty, accessors). +// The prototype-only twins (zero-raw-field scan, pointer-write fallback, +// `layout_transfer` on move) were dropped — `typed_shape.rs` and +// `object_closure_slots.rs` already pin those paths for the default installer. #[test] fn test_layout_scan_trace_counts_raw_numeric_object_fields() { @@ -41,8 +27,8 @@ fn test_layout_scan_trace_counts_raw_numeric_object_fields() { .expect("test requested GC trace capture"); let obj = crate::object::js_object_alloc(0, 2); - crate::object::js_object_set_unboxed_f64_field(obj, 0, 1.25); - crate::object::js_object_set_unboxed_f64_field(obj, 1, -2.5); + crate::object::js_object_set_field(obj, 0, crate::value::JSValue::number(1.25)); + crate::object::js_object_set_field(obj, 1, crate::value::JSValue::number(-2.5)); let raw_mask = [0b11u64]; js_gc_init_typed_shape_layout( obj as u64, @@ -94,7 +80,11 @@ fn test_layout_scan_trace_counts_mixed_raw_numeric_object_fields() { .expect("test requested GC trace capture"); let obj = crate::object::js_object_alloc(0, 2); - crate::object::js_object_set_unboxed_f64_field(obj, 0, f64::from_bits(0x1000)); + crate::object::js_object_set_field( + obj, + 0, + crate::value::JSValue::number(f64::from_bits(0x1000)), + ); let child = crate::string::js_string_from_bytes(b"mixed-child".as_ptr(), 11); crate::object::js_object_set_field(obj, 1, crate::value::JSValue::string_ptr(child)); let raw_mask = [0b01u64]; @@ -135,70 +125,6 @@ fn test_layout_scan_trace_counts_mixed_raw_numeric_object_fields() { clear_mark_seeds(); } -#[test] -fn test_unboxed_object_pointer_write_to_raw_slot_falls_back_and_traces() { - clear_marks(); - clear_mark_seeds(); - - let obj = crate::object::js_object_alloc(0, 2); - crate::object::js_object_set_unboxed_f64_field(obj, 0, 1.0); - crate::object::js_object_set_unboxed_f64_field(obj, 1, 2.0); - js_gc_init_unboxed_object_layout(obj as u64, 2, 0b11, 0); - assert_eq!(test_layout_pointer_slot_count(obj as usize, 2), Some(0)); - - let child = crate::string::js_string_from_bytes(b"unboxed-child".as_ptr(), 13); - let child_header = unsafe { header_from_user_ptr(child as *mut u8) }; - crate::object::js_object_set_field(obj, 0, crate::value::JSValue::string_ptr(child)); - - assert_eq!( - test_layout_pointer_slot_count(obj as usize, 2), - None, - "non-number writes to raw f64 slots must deopt to full scanning" - ); - - let valid_ptrs = build_valid_pointer_set(); - assert!(try_mark_value( - POINTER_TAG | (obj as u64 & POINTER_MASK), - &valid_ptrs - )); - test_reset_trace_slot_reads(); - trace_marked_objects(&valid_ptrs); - assert_eq!(test_trace_slot_reads(), 2); - unsafe { - assert_ne!((*child_header).gc_flags & GC_FLAG_MARKED, 0); - } - - clear_marks(); - clear_mark_seeds(); -} - -#[test] -fn test_unboxed_object_descriptor_transfers_on_object_move() { - clear_marks(); - clear_mark_seeds(); - - let src = crate::object::js_object_alloc(0, 2); - let dst = crate::object::js_object_alloc(0, 2); - crate::object::js_object_set_unboxed_f64_field(src, 0, 3.0); - crate::object::js_object_set_unboxed_f64_field(src, 1, 4.0); - js_gc_init_unboxed_object_layout(src as u64, 2, 0b11, 0); - - unsafe { - layout_transfer(src as *mut u8, dst as *mut u8); - } - - assert_eq!(test_layout_pointer_slot_count(dst as usize, 2), Some(0)); - crate::object::js_object_set_unboxed_f64_field(dst, 1, 5.0); - assert_eq!(test_layout_pointer_slot_count(dst as usize, 2), Some(0)); - - let child = crate::string::js_string_from_bytes(b"moved-child".as_ptr(), 11); - crate::object::js_object_set_field(dst, 1, crate::value::JSValue::string_ptr(child)); - assert_eq!(test_layout_pointer_slot_count(dst as usize, 2), None); - - clear_marks(); - clear_mark_seeds(); -} - #[test] fn test_raw_numeric_object_descriptor_transfers_on_copying_minor_and_skips_raw_slots() { let _guard = CopyingNurseryTestGuard::new(1); @@ -206,9 +132,9 @@ fn test_raw_numeric_object_descriptor_transfers_on_copying_minor_and_skips_raw_s let child = young_leaf(); let obj = crate::object::js_object_alloc(0, 3); - crate::object::js_object_set_unboxed_f64_field(obj, 0, 10.5); + crate::object::js_object_set_field(obj, 0, crate::value::JSValue::number(10.5)); crate::object::js_object_set_field(obj, 1, crate::value::JSValue::from_bits(ptr_bits(child))); - crate::object::js_object_set_unboxed_f64_field(obj, 2, -3.25); + crate::object::js_object_set_field(obj, 2, crate::value::JSValue::number(-3.25)); let raw_mask = [0b101u64]; let pointer_mask = [0b010u64]; js_gc_init_typed_shape_layout( @@ -256,7 +182,7 @@ fn test_raw_numeric_object_descriptor_transfers_on_copying_minor_and_skips_raw_s ); } -fn unboxed_point_for_shape_change_test(shape_id: u32) -> *mut crate::object::ObjectHeader { +fn typed_point_for_shape_change_test(shape_id: u32) -> *mut crate::object::ObjectHeader { let packed_keys = b"x\0y\0"; let obj = crate::object::js_object_alloc_with_shape( shape_id, @@ -264,9 +190,17 @@ fn unboxed_point_for_shape_change_test(shape_id: u32) -> *mut crate::object::Obj packed_keys.as_ptr(), packed_keys.len() as u32, ); - crate::object::js_object_set_unboxed_f64_field(obj, 0, 1.0); - crate::object::js_object_set_unboxed_f64_field(obj, 1, 2.0); - js_gc_init_unboxed_object_layout(obj as u64, 2, 0b11, 0); + crate::object::js_object_set_field(obj, 0, crate::value::JSValue::number(1.0)); + crate::object::js_object_set_field(obj, 1, crate::value::JSValue::number(2.0)); + let raw_mask = [0b11u64]; + js_gc_init_typed_shape_layout( + obj as u64, + 2, + raw_mask.as_ptr(), + raw_mask.len() as u32, + std::ptr::null(), + 0, + ); assert_eq!(test_layout_pointer_slot_count(obj as usize, 2), Some(0)); obj } @@ -290,18 +224,18 @@ fn descriptor_object_with_single_field( } #[test] -fn test_unboxed_object_dynamic_added_property_falls_back() { +fn test_typed_object_dynamic_added_property_falls_back() { clear_marks(); clear_mark_seeds(); - let obj = unboxed_point_for_shape_change_test(86_101); + let obj = typed_point_for_shape_change_test(86_101); let z_key = crate::string::js_string_from_bytes(b"z".as_ptr(), 1); crate::object::js_object_set_field_by_name(obj, z_key, 3.0); assert_eq!( test_layout_pointer_slot_count(obj as usize, 3), None, - "adding a dynamic property must invalidate the exact unboxed shape" + "adding a dynamic property must invalidate the exact typed shape" ); clear_marks(); @@ -309,18 +243,18 @@ fn test_unboxed_object_dynamic_added_property_falls_back() { } #[test] -fn test_unboxed_object_delete_falls_back() { +fn test_typed_object_delete_falls_back() { clear_marks(); clear_mark_seeds(); - let obj = unboxed_point_for_shape_change_test(86_102); + let obj = typed_point_for_shape_change_test(86_102); let x_key = crate::string::js_string_from_bytes(b"x".as_ptr(), 1); assert_eq!(crate::object::js_object_delete_field(obj, x_key), 1); assert_eq!( test_layout_pointer_slot_count(obj as usize, 1), None, - "delete shifts keys/fields and must invalidate the exact unboxed shape" + "delete shifts keys/fields and must invalidate the exact typed shape" ); clear_marks(); @@ -328,11 +262,11 @@ fn test_unboxed_object_delete_falls_back() { } #[test] -fn test_unboxed_object_define_property_falls_back() { +fn test_typed_object_define_property_falls_back() { clear_marks(); clear_mark_seeds(); - let obj = unboxed_point_for_shape_change_test(86_103); + let obj = typed_point_for_shape_change_test(86_103); let x_key = crate::string::js_string_from_bytes(b"x".as_ptr(), 1); let desc = descriptor_object_with_single_field(86_104, b"value", crate::value::JSValue::number(9.0)); @@ -346,7 +280,7 @@ fn test_unboxed_object_define_property_falls_back() { assert_eq!( test_layout_pointer_slot_count(obj as usize, 2), None, - "Object.defineProperty must invalidate the exact unboxed shape even for existing keys" + "Object.defineProperty must invalidate the exact typed shape even for existing keys" ); clear_marks(); @@ -354,11 +288,11 @@ fn test_unboxed_object_define_property_falls_back() { } #[test] -fn test_unboxed_object_accessor_define_property_falls_back() { +fn test_typed_object_accessor_define_property_falls_back() { clear_marks(); clear_mark_seeds(); - let obj = unboxed_point_for_shape_change_test(86_105); + let obj = typed_point_for_shape_change_test(86_105); let x_key = crate::string::js_string_from_bytes(b"x".as_ptr(), 1); // #2817: an accessor descriptor's `get` must be callable — a non-function // value now throws. Use a real (capture-less) closure as the getter so we @@ -379,7 +313,7 @@ fn test_unboxed_object_accessor_define_property_falls_back() { assert_eq!( test_layout_pointer_slot_count(obj as usize, 2), None, - "accessor descriptors must invalidate the exact unboxed shape" + "accessor descriptors must invalidate the exact typed shape" ); clear_marks(); diff --git a/crates/perry-runtime/src/gc/tests/layout_trace/typed_shape.rs b/crates/perry-runtime/src/gc/tests/layout_trace/typed_shape.rs index 0f12a03f41..d7840aff82 100644 --- a/crates/perry-runtime/src/gc/tests/layout_trace/typed_shape.rs +++ b/crates/perry-runtime/src/gc/tests/layout_trace/typed_shape.rs @@ -75,7 +75,7 @@ fn test_typed_shape_descriptor_tracks_raw_numeric_slots() { clear_mark_seeds(); let obj = crate::object::js_object_alloc(0, 2); - crate::object::js_object_set_unboxed_f64_field(obj, 0, 1.5); + crate::object::js_object_set_field(obj, 0, crate::value::JSValue::number(1.5)); crate::object::js_object_set_field(obj, 1, crate::value::JSValue::number(2.5)); let raw_mask = [0b01u64]; js_gc_init_typed_shape_layout( @@ -149,7 +149,7 @@ fn test_typed_shape_descriptor_visible_for_shape_keyed_objects() { 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, 0, crate::value::JSValue::number(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, @@ -231,7 +231,7 @@ fn test_shape_keyed_typed_layout_survives_layout_transfer() { packed.len() as u32, ); let src = crate::object::js_object_alloc_class_inline_keys(0x6964_01, 0, 2, keys); - crate::object::js_object_set_unboxed_f64_field(src, 0, 1.5); + crate::object::js_object_set_field(src, 0, crate::value::JSValue::number(1.5)); crate::object::js_object_set_field(src, 1, crate::value::JSValue::number(2.5)); let raw_mask = [0b01u64]; js_gc_init_typed_shape_layout( @@ -281,7 +281,7 @@ fn test_shape_keyed_typed_layout_survives_layout_transfer() { // that must NOT take the shared entry with it: an untouched sibling still // reads the shape descriptor. let sibling = crate::object::js_object_alloc_class_inline_keys(0x6964_01, 0, 2, keys); - crate::object::js_object_set_unboxed_f64_field(sibling, 0, 7.5); + crate::object::js_object_set_field(sibling, 0, crate::value::JSValue::number(7.5)); crate::object::js_object_set_field(sibling, 1, crate::value::JSValue::number(8.5)); js_gc_init_typed_shape_layout( sibling as u64, @@ -312,7 +312,7 @@ fn test_shape_keyed_typed_layout_survives_copying_minor() { packed.len() as u32, ); let obj = crate::object::js_object_alloc_class_inline_keys(0x6964_02, 0, 2, keys); - crate::object::js_object_set_unboxed_f64_field(obj, 0, 10.5); + crate::object::js_object_set_field(obj, 0, crate::value::JSValue::number(10.5)); crate::object::js_object_set_field(obj, 1, crate::value::JSValue::number(-3.25)); let raw_mask = [0b01u64]; js_gc_init_typed_shape_layout( @@ -359,7 +359,7 @@ fn test_typed_shape_raw_numeric_slots_accept_pointer_like_f64_bits() { let obj = crate::object::js_object_alloc(0, 2); let pointer_like_number = f64::from_bits(0x1000); - crate::object::js_object_set_unboxed_f64_field(obj, 0, pointer_like_number); + crate::object::js_object_set_field(obj, 0, crate::value::JSValue::number(pointer_like_number)); let child = crate::string::js_string_from_bytes(b"mixed-child".as_ptr(), 11); crate::object::js_object_set_field(obj, 1, crate::value::JSValue::string_ptr(child)); @@ -378,7 +378,11 @@ fn test_typed_shape_raw_numeric_slots_accept_pointer_like_f64_bits() { assert_eq!(test_layout_pointer_slot_count(obj as usize, 2), Some(1)); let next_pointer_like_number = f64::from_bits(0x2000); - crate::object::js_object_set_unboxed_f64_field(obj, 0, next_pointer_like_number); + crate::object::js_object_set_field( + obj, + 0, + crate::value::JSValue::number(next_pointer_like_number), + ); assert!( layout_typed_raw_f64_slot_for_user(obj as usize, 0), "raw f64 slots must not be downgraded by numeric payload bits that resemble raw pointers" @@ -396,7 +400,7 @@ fn test_typed_shape_descriptor_rejects_nanbox_non_number_tags() { let raw_mask = [0b1u64]; let obj = crate::object::js_object_alloc(0, 1); - crate::object::js_object_set_unboxed_f64_field(obj, 0, 1.5); + crate::object::js_object_set_field(obj, 0, crate::value::JSValue::number(1.5)); js_gc_init_typed_shape_layout( obj as u64, 1, @@ -416,7 +420,7 @@ fn test_typed_shape_descriptor_rejects_nanbox_non_number_tags() { assert_eq!(test_layout_pointer_slot_count(obj as usize, 1), None); let handle_obj = crate::object::js_object_alloc(0, 1); - crate::object::js_object_set_unboxed_f64_field(handle_obj, 0, 2.5); + crate::object::js_object_set_field(handle_obj, 0, crate::value::JSValue::number(2.5)); js_gc_init_typed_shape_layout( handle_obj as u64, 1, diff --git a/crates/perry-runtime/src/lib.rs b/crates/perry-runtime/src/lib.rs index 617314833e..fc03817728 100644 --- a/crates/perry-runtime/src/lib.rs +++ b/crates/perry-runtime/src/lib.rs @@ -286,9 +286,8 @@ pub use object::js_object_set_field_by_name; pub use object::{ js_object_alloc, js_object_alloc_null_proto, js_object_alloc_with_shape, js_object_entries, js_object_get_field, js_object_get_field_by_name, js_object_get_field_by_name_f64, - js_object_get_own_field_or_undef, js_object_get_unboxed_f64_field, js_object_keys, - js_object_set_field, js_object_set_field_f64, js_object_set_keys, - js_object_set_unboxed_f64_field, js_object_values, + js_object_get_own_field_or_undef, js_object_keys, js_object_set_field, js_object_set_field_f64, + js_object_set_keys, js_object_values, }; pub use promise::{js_is_promise, js_promise_run_microtasks, js_promise_state, js_promise_value}; pub use promise::{ diff --git a/crates/perry-runtime/src/object/field_get_set.rs b/crates/perry-runtime/src/object/field_get_set.rs index 497436d078..e523bdba75 100644 --- a/crates/perry-runtime/src/object/field_get_set.rs +++ b/crates/perry-runtime/src/object/field_get_set.rs @@ -179,10 +179,9 @@ pub use enumeration::{ js_object_keys_value, js_object_values, js_object_values_value, }; pub use field_ops::{ - js_object_free, js_object_get_class_id, js_object_get_field_f64, - js_object_get_unboxed_f64_field, js_object_set_field, js_object_set_field_by_index, - js_object_set_field_f64, js_object_set_keys, js_object_set_unboxed_f64_field, - js_object_to_value, js_value_to_object, + js_object_free, js_object_get_class_id, js_object_get_field_f64, js_object_set_field, + js_object_set_field_by_index, js_object_set_field_f64, js_object_set_keys, js_object_to_value, + js_value_to_object, }; pub use get_field_by_name::js_object_get_field_by_name; pub(crate) use get_field_by_name_tail::get_field_by_name_object_tail; diff --git a/crates/perry-runtime/src/object/field_get_set/field_ops.rs b/crates/perry-runtime/src/object/field_get_set/field_ops.rs index 07b15cc0c6..edb2770da1 100644 --- a/crates/perry-runtime/src/object/field_get_set/field_ops.rs +++ b/crates/perry-runtime/src/object/field_get_set/field_ops.rs @@ -244,71 +244,6 @@ pub extern "C" fn js_object_set_field_f64(obj: *mut ObjectHeader, field_index: u js_object_set_field(obj, field_index, JSValue::from_bits(value.to_bits())); } -/// Store a raw f64 into an object field slot for the unboxed numeric-field prototype. -/// -/// This is only intended for construction sites whose static type has already -/// proven a raw-number slot. Dynamic writes still go through the normal setters, -/// which deopt the typed descriptor before tracing non-number values. -#[no_mangle] -pub extern "C" fn js_object_set_unboxed_f64_field( - obj: *mut ObjectHeader, - field_index: u32, - value: f64, -) { - let obj = { - let b = obj as u64; - let t = b >> 48; - if t >= 0x7FF8 { - if t == 0x7FFC - || (b & 0x0000_FFFF_FFFF_FFFF) == 0 - || (b & 0x0000_FFFF_FFFF_FFFF) < 0x10000 - { - return; - } - (b & 0x0000_FFFF_FFFF_FFFF) as *mut ObjectHeader - } else { - obj - } - }; - if obj.is_null() || (obj as usize) < 0x10000 { - return; - } - unsafe { - let gc = (obj as *const u8).sub(crate::gc::GC_HEADER_SIZE) as *const crate::gc::GcHeader; - if (*gc)._reserved & crate::gc::OBJ_FLAG_FROZEN != 0 { - return; - } - let stored_field_count = (*obj).field_count; - let alloc_limit = - std::cmp::max(stored_field_count, crate::object::INLINE_SLOT_FLOOR as u32); - if field_index >= alloc_limit { - eprintln!( - "[PERRY WARN] js_object_set_unboxed_f64_field: OOB write field_index={} alloc_limit={} (field_count={}) obj={:p} class_id={}", - field_index, alloc_limit, stored_field_count, obj, (*obj).class_id - ); - return; - } - let bits = value.to_bits(); - let fields_ptr = (obj as *mut u8).add(std::mem::size_of::()) as *mut u64; - let slot = fields_ptr.add(field_index as usize); - crate::gc::runtime_store_jsvalue_slot( - obj as usize, - slot as usize, - field_index as usize, - bits, - ); - } -} - -/// Read a raw f64 object field slot used by the unboxed numeric-field prototype. -#[no_mangle] -pub extern "C" fn js_object_get_unboxed_f64_field( - obj: *const ObjectHeader, - field_index: u32, -) -> f64 { - f64::from_bits(js_object_get_field(obj, field_index).bits()) -} - /// Set a field by index with a raw f64 value (for dynamic object creation) /// This is a convenience wrapper that takes field_index as u32 and value as f64. /// Honors `Object.freeze` and per-key `writable: false` descriptors so codegen diff --git a/crates/perry-runtime/src/typed_feedback.rs b/crates/perry-runtime/src/typed_feedback.rs index a2a8d83bc4..03fec4b0ca 100644 --- a/crates/perry-runtime/src/typed_feedback.rs +++ b/crates/perry-runtime/src/typed_feedback.rs @@ -2575,7 +2575,12 @@ pub extern "C" fn js_typed_feedback_object_set_unboxed_f64_field( object_key_matches_field(obj, key, field_index) && is_plain_number_bits(value.to_bits()), ); if pass { - crate::object::js_object_set_unboxed_f64_field(obj, field_index, value); + // The `js_object_set_unboxed_f64_field` prototype setter this wrapper + // once used was deleted (Phase 4b cleanup) — its store path was + // bit-identical to the plain indexed setter. The symbol name stays + // (it is a `check_runtime_symbols.sh` sentinel and part of the #854 + // typed-feedback foundation); only the fast path changed. + crate::object::js_object_set_field(obj, field_index, crate::JSValue::number(value)); } else { record_fallback_call(site_id); crate::object::js_object_set_field_by_name(obj, key, value); diff --git a/crates/perry-runtime/src/typed_feedback/tests.rs b/crates/perry-runtime/src/typed_feedback/tests.rs index bbb17f5019..7465c9d4a1 100644 --- a/crates/perry-runtime/src/typed_feedback/tests.rs +++ b/crates/perry-runtime/src/typed_feedback/tests.rs @@ -1653,7 +1653,7 @@ fn typed_feedback_class_field_get_guard_requires_raw_f64_layout_when_requested() let class_id = 0x7EED_0043; let (obj, expected_keys, key_x, receiver) = class_instance(class_id, b"x"); - crate::object::js_object_set_unboxed_f64_field(obj, 0, 5.0); + crate::object::js_object_set_field(obj, 0, crate::JSValue::number(5.0)); let raw_mask = [0b1u64]; crate::gc::js_gc_init_typed_shape_layout( obj as u64, @@ -1689,7 +1689,7 @@ fn typed_feedback_class_field_set_guard_requires_raw_f64_value_and_layout() { let class_id = 0x7EED_0044; let (obj, expected_keys, key_x, receiver) = class_instance(class_id, b"x"); - crate::object::js_object_set_unboxed_f64_field(obj, 0, 1.0); + crate::object::js_object_set_field(obj, 0, crate::JSValue::number(1.0)); let raw_mask = [0b1u64]; crate::gc::js_gc_init_typed_shape_layout( obj as u64, diff --git a/crates/perry/src/commands/compile/build_cache.rs b/crates/perry/src/commands/compile/build_cache.rs index 9e534f12b3..a6ed50122d 100644 --- a/crates/perry/src/commands/compile/build_cache.rs +++ b/crates/perry/src/commands/compile/build_cache.rs @@ -43,7 +43,6 @@ const BUILD_CACHE_ENV_VARS: &[&str] = &[ "PERRY_INLINE_SHADOW_SLOT", "PERRY_DISABLE_BUFFER_FAST_PATH", "PERRY_VERIFY_NATIVE_REGIONS", - "PERRY_UNBOXED_OBJECT_FIELDS", // #6125: the resolved CPU baseline (promoted from --march / perry.toml // [build] by promote_cpu_baseline_env before this probe runs). Flipping // it must invalidate the build-level no-op check, not just per-object diff --git a/crates/perry/src/commands/compile/object_cache.rs b/crates/perry/src/commands/compile/object_cache.rs index 2c95d380ae..257ac5110a 100644 --- a/crates/perry/src/commands/compile/object_cache.rs +++ b/crates/perry/src/commands/compile/object_cache.rs @@ -231,7 +231,7 @@ fn stable_type_key(ty: &perry_hir::types::Type) -> String { /// `PERRY_DEBUG_INIT`, `PERRY_DEBUG_SYMBOLS`, `PERRY_LLVM_CLANG`, /// `PERRY_WRITE_BARRIERS`, `PERRY_SHADOW_STACK`, /// `PERRY_DISABLE_BUFFER_FAST_PATH`, `PERRY_VERIFY_NATIVE_REGIONS`, -/// `PERRY_UNBOXED_OBJECT_FIELDS`, and `PERRY_TARGET_CPU`. See the env-var +/// and `PERRY_TARGET_CPU`. See the env-var /// block at the bottom of this function for the rationale. /// /// NOT captured in the key: the host CPU. By default (`PERRY_TARGET_CPU` @@ -768,8 +768,6 @@ fn compute_object_cache_key_with_env( // changes Buffer/Uint8Array lowering. // - PERRY_VERIFY_NATIVE_REGIONS=1 overrides CompileOptions and must // not be bypassed by a stale cache hit. - // - PERRY_UNBOXED_OBJECT_FIELDS=1 changes object-literal layout - // lowering for exact typed object shapes. // - PERRY_TARGET_CPU (#6125, set directly or promoted from `--march` / // perry.toml `[build] march`/`native_tuning`) changes the clang // `-march`/`-mcpu` tuning flag, i.e. which instruction-set baseline @@ -833,12 +831,6 @@ fn compute_object_cache_key_with_env( .as_deref() .unwrap_or(""), ); - h.field( - "env_unboxed_object_fields", - env_var("PERRY_UNBOXED_OBJECT_FIELDS") - .as_deref() - .unwrap_or(""), - ); h.field( "env_target_cpu", env_var("PERRY_TARGET_CPU").as_deref().unwrap_or(""), diff --git a/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs b/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs index d2dec5c4f9..1f62918914 100644 --- a/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs +++ b/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs @@ -588,7 +588,6 @@ fn key_changes_with_codegen_env_vars() { "PERRY_GC_SAFEPOINT_ONLY", "PERRY_DISABLE_BUFFER_FAST_PATH", "PERRY_VERIFY_NATIVE_REGIONS", - "PERRY_UNBOXED_OBJECT_FIELDS", "PERRY_TARGET_CPU", // Codegen tuning/emission toggles (#6394). "PERRY_TYPED_FEEDBACK", diff --git a/scripts/addr_class_ratchet_baseline.txt b/scripts/addr_class_ratchet_baseline.txt index e2a54714f3..7d2dcf3009 100644 --- a/scripts/addr_class_ratchet_baseline.txt +++ b/scripts/addr_class_ratchet_baseline.txt @@ -109,7 +109,7 @@ handle-floor | crates/perry-runtime/src/object/descriptor_state.rs | 1 handle-floor | crates/perry-runtime/src/object/descriptors.rs | 2 handle-floor | crates/perry-runtime/src/object/field_get_set/accessors.rs | 2 handle-floor | crates/perry-runtime/src/object/field_get_set/enumeration.rs | 4 -handle-floor | crates/perry-runtime/src/object/field_get_set/field_ops.rs | 4 +handle-floor | crates/perry-runtime/src/object/field_get_set/field_ops.rs | 3 handle-floor | crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs | 3 handle-floor | crates/perry-runtime/src/object/field_get_set/has_property.rs | 2 handle-floor | crates/perry-runtime/src/object/field_get_set/ic_miss.rs | 4