diff --git a/changelog.d/11702-gc-effects-tables-and-symbol-thread-exit-test.md b/changelog.d/11702-gc-effects-tables-and-symbol-thread-exit-test.md new file mode 100644 index 0000000000..b9f1dfd4b0 --- /dev/null +++ b/changelog.d/11702-gc-effects-tables-and-symbol-thread-exit-test.md @@ -0,0 +1,2 @@ +- **fix(gc-effects): regenerate the macOS-aarch64 and Windows-x86_64 GC call-effects tables after #11682** (#11695). #11682 regenerated only the Linux table. On the other two targets, `js_abort_controller_signal`, `js_abort_signal_is_aborted`, `js_abort_signal_throw_if_aborted` and `js_error_is_error` were committed `Leaf`/`ThrowOnly` while their archives prove `Reenters`, so codegen could skip a statepoint the runtime needs. The tables are the CI artifacts from main run 36653831017. They also pick up the new `js_event_target_subclass_init` and the pre-existing safe drift. +- **test(stdlib): keep the symbol thread-exit probe live** (#11696). Since #11682, ordinary objects keep their symbol properties on the object (`shaped_symbols`), so the address-keyed side tables are empty for them. `thread_exit_releases_the_threads_symbol_side_table_entries` now checks table release on an array holder and a class static. It asserts that an ordinary object's symbol value, attrs and accessor live on the object, using the new `symbol::symbol_on_object_for_test` probe, and never in the tables. Both halves were sabotage-checked red. diff --git a/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv b/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv index ee8b2811dc..0517dd0ca6 100644 --- a/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv +++ b/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv @@ -21,17 +21,17 @@ global_this_builtin_noop_thunk Leaf js_abort_controller_abort Reenters js_abort_controller_abort_reason Reenters js_abort_controller_new Reenters -js_abort_controller_signal Leaf +js_abort_controller_signal Reenters js_abort_error_value Reenters js_abort_signal_abort Reenters js_abort_signal_add_listener Reenters js_abort_signal_any Reenters -js_abort_signal_is_aborted Leaf +js_abort_signal_is_aborted Reenters js_abort_signal_listener_count Reenters js_abort_signal_listeners_copy Reenters js_abort_signal_remove_listener Reenters js_abort_signal_resolve_ptr Leaf -js_abort_signal_throw_if_aborted ThrowOnly +js_abort_signal_throw_if_aborted Reenters js_abort_signal_timeout Reenters js_add Leaf js_add_throw_if_symbol ThrowOnly @@ -509,7 +509,7 @@ js_bun_sqlite_database_filename Reenters js_bun_sqlite_database_new Reenters js_bun_sqlite_database_query Reenters js_bun_sqlite_database_run Reenters -js_bun_sqlite_database_transaction Reenters +js_bun_sqlite_database_transaction ThrowOnly js_bun_sqlite_statement_finalize Reenters js_bun_sqlite_statement_safe_integers Reenters js_bun_sqlite_statement_values Reenters @@ -600,18 +600,20 @@ js_class_prototype_method_value Reenters js_class_register_capture_values Leaf js_class_register_static_field Reenters js_class_register_static_symbol Reenters +js_class_static_call_guard Reenters js_class_static_field_get Reenters js_class_static_field_put Reenters js_class_static_method_call Reenters +js_class_static_value_call_guard Reenters js_class_value Reenters js_clear_exception Leaf js_clear_immediate_value Reenters js_clear_interval_value Reenters js_clear_timeout_value Reenters -js_closure_alloc Reenters -js_closure_alloc_init Reenters -js_closure_alloc_singleton Reenters -js_closure_alloc_with_captures_singleton Reenters +js_closure_alloc AllocOnly +js_closure_alloc_init AllocOnly +js_closure_alloc_singleton AllocOnly +js_closure_alloc_with_captures_singleton AllocOnly js_closure_call0 Reenters js_closure_call1 Reenters js_closure_call10 Reenters @@ -643,7 +645,7 @@ js_closure_set_capture_bits Leaf js_closure_set_capture_f64 Leaf js_closure_set_capture_ptr Leaf js_closure_unbox_callee_checked ThrowOnly -js_closure_unbox_callee_checked_rebind Reenters +js_closure_unbox_callee_checked_rebind ThrowOnly js_closure_v8_callback Reenters js_cluster_disconnect Reenters js_cluster_emit Reenters @@ -685,7 +687,7 @@ js_console_group_begin Leaf js_console_group_end Leaf js_console_info_spread Reenters js_console_log Leaf -js_console_log_as_closure Reenters +js_console_log_as_closure AllocOnly js_console_log_dynamic Reenters js_console_log_i32 Leaf js_console_log_i64 Leaf @@ -982,13 +984,13 @@ js_dns_set_servers Reenters js_dom_exception_new Reenters js_dom_exception_subclass_init Reenters js_domain_add Reenters -js_domain_bind Reenters +js_domain_bind AllocOnly js_domain_create Reenters js_domain_emit Reenters js_domain_emit_error Reenters js_domain_enter Reenters js_domain_exit Reenters -js_domain_intercept Reenters +js_domain_intercept AllocOnly js_domain_native_dispatch Reenters js_domain_on Reenters js_domain_remove Reenters @@ -1035,7 +1037,7 @@ js_error_get_kind Leaf js_error_get_message AllocOnly js_error_get_name AllocOnly js_error_get_stack AllocOnly -js_error_is_error Leaf +js_error_is_error Reenters js_error_new AllocOnly js_error_new_from_value Reenters js_error_new_kind_from_value Reenters @@ -1102,6 +1104,7 @@ js_event_target_new Reenters js_event_target_remove_event_listener Reenters js_event_target_remove_event_listener_with_options Reenters js_event_target_set_max_listeners Reenters +js_event_target_subclass_init Reenters js_events_add_abort_listener Reenters js_events_get_event_listeners Reenters js_events_get_max_listeners Reenters @@ -1456,7 +1459,7 @@ js_import_meta_resolve Reenters js_import_meta_resolve_value Reenters js_in_operator Reenters js_in_operator_presence_ic Reenters -js_inherited_read_cache_hit_f64 Reenters +js_inherited_read_cache_hit_f64 Leaf js_inherited_read_cache_stats Leaf js_inline_arena_slow_alloc AllocOnly js_inline_arena_state Leaf @@ -2180,6 +2183,7 @@ js_object_set_symbol_method Reenters js_object_set_symbol_property Reenters js_object_shape_id_for_class_keys Reenters js_object_shape_id_for_class_keys_live Reenters +js_object_shape_id_for_class_keys_static Reenters js_object_shape_id_for_keys Reenters js_object_super_call Reenters js_object_super_get Reenters @@ -2716,6 +2720,8 @@ js_regexp_test Reenters js_regexp_to_string Reenters js_region_guard_pack Reenters js_region_guard_prime Reenters +js_region_loop_pack Leaf +js_region_loop_prime Leaf js_register_anon_shape_class_id Reenters js_register_aux_has_active Leaf js_register_aux_pump Leaf @@ -2743,6 +2749,7 @@ js_register_class_source_static Leaf js_register_class_static_getter Reenters js_register_class_static_method Reenters js_register_class_static_method_bind_length Reenters +js_register_class_static_method_entry Reenters js_register_class_static_setter Reenters js_register_class_string_member_order Leaf js_register_class_to_string_tag Reenters @@ -2784,7 +2791,6 @@ js_register_handle_property_set_dispatch Leaf js_register_handle_property_set_dispatch_extension Leaf js_register_handle_prototype_dispatch Leaf js_register_http_agent_handle_probe Leaf -js_register_imported_class_shape_slot Reenters js_register_jsx_render_stream Leaf js_register_net_socket_handle_probe Leaf js_register_node_stream_web_adapter_callbacks Leaf @@ -2974,6 +2980,9 @@ js_shadow_slot_get Leaf js_shadow_slot_set Leaf js_shadow_state_addr Leaf js_shape_ordinary_inline_slot_for_key Reenters +js_shape_register_static_seed Leaf +js_shape_run_static_seed Reenters +js_shape_seed_plain Reenters js_shared_array_buffer_new ThrowOnly js_shared_array_buffer_new_value Reenters js_sharp_blur Reenters @@ -3017,9 +3026,12 @@ js_sqlite_stmt_get Reenters js_sqlite_stmt_raw Reenters js_sqlite_stmt_run Reenters js_sqlite_transaction Reenters +js_stack_overflow ThrowOnly js_state_get Reenters js_state_init Reenters js_state_set Reenters +js_static_method_entry_enter Reenters +js_static_method_entry_leave Leaf js_static_this_arm_classref Reenters js_static_this_arm_value Leaf js_static_this_resolve Leaf @@ -3117,6 +3129,7 @@ js_string_normalize Reenters js_string_pad_end ThrowOnly js_string_pad_fill Reenters js_string_pad_start ThrowOnly +js_string_pool_atom AllocOnly js_string_position_to_index Leaf js_string_print Leaf js_string_raw Reenters @@ -3515,10 +3528,10 @@ js_url_set_search Reenters js_url_set_username Reenters js_url_to_http_options Reenters js_util_aborted Reenters -js_util_callbackify Reenters +js_util_callbackify ThrowOnly js_util_convert_process_signal_to_exit_code ThrowOnly js_util_debuglog Reenters -js_util_deprecate Reenters +js_util_deprecate ThrowOnly js_util_diff Reenters js_util_errno_exception Reenters js_util_exception_with_host_port Reenters @@ -3600,7 +3613,7 @@ js_v8_get_heap_statistics Reenters js_v8_is_building_snapshot Leaf js_v8_namespace Reenters js_v8_noop_undefined Leaf -js_v8_promise_hook_register Reenters +js_v8_promise_hook_register AllocOnly js_v8_promise_hooks_create_hook Reenters js_v8_promise_hooks_on_after Reenters js_v8_promise_hooks_on_before Reenters diff --git a/crates/perry-codegen/src/gc_effects/windows-x86_64.tsv b/crates/perry-codegen/src/gc_effects/windows-x86_64.tsv index e938fc842b..5609aee72c 100644 --- a/crates/perry-codegen/src/gc_effects/windows-x86_64.tsv +++ b/crates/perry-codegen/src/gc_effects/windows-x86_64.tsv @@ -21,17 +21,17 @@ global_this_builtin_noop_thunk Leaf js_abort_controller_abort Reenters js_abort_controller_abort_reason Reenters js_abort_controller_new Reenters -js_abort_controller_signal Leaf +js_abort_controller_signal Reenters js_abort_error_value Reenters js_abort_signal_abort Reenters js_abort_signal_add_listener Reenters js_abort_signal_any Reenters -js_abort_signal_is_aborted Leaf +js_abort_signal_is_aborted Reenters js_abort_signal_listener_count Reenters js_abort_signal_listeners_copy Reenters js_abort_signal_remove_listener Reenters js_abort_signal_resolve_ptr Leaf -js_abort_signal_throw_if_aborted ThrowOnly +js_abort_signal_throw_if_aborted Reenters js_abort_signal_timeout Reenters js_add Leaf js_add_throw_if_symbol Reenters @@ -600,18 +600,20 @@ js_class_prototype_method_value Reenters js_class_register_capture_values Leaf js_class_register_static_field Reenters js_class_register_static_symbol Reenters +js_class_static_call_guard Reenters js_class_static_field_get Reenters js_class_static_field_put Reenters js_class_static_method_call Reenters +js_class_static_value_call_guard Reenters js_class_value Reenters js_clear_exception Leaf js_clear_immediate_value Reenters js_clear_interval_value Reenters js_clear_timeout_value Reenters -js_closure_alloc Reenters -js_closure_alloc_init Reenters -js_closure_alloc_singleton Reenters -js_closure_alloc_with_captures_singleton Reenters +js_closure_alloc AllocOnly +js_closure_alloc_init AllocOnly +js_closure_alloc_singleton AllocOnly +js_closure_alloc_with_captures_singleton AllocOnly js_closure_call0 Reenters js_closure_call1 Reenters js_closure_call10 Reenters @@ -685,7 +687,7 @@ js_console_group_begin Leaf js_console_group_end Leaf js_console_info_spread Reenters js_console_log Reenters -js_console_log_as_closure Reenters +js_console_log_as_closure AllocOnly js_console_log_dynamic Reenters js_console_log_i32 Reenters js_console_log_i64 Reenters @@ -982,13 +984,13 @@ js_dns_set_servers Reenters js_dom_exception_new Reenters js_dom_exception_subclass_init Reenters js_domain_add Reenters -js_domain_bind Reenters +js_domain_bind AllocOnly js_domain_create Reenters js_domain_emit Reenters js_domain_emit_error Reenters js_domain_enter Reenters js_domain_exit Reenters -js_domain_intercept Reenters +js_domain_intercept AllocOnly js_domain_native_dispatch Reenters js_domain_on Reenters js_domain_remove Reenters @@ -1035,7 +1037,7 @@ js_error_get_kind Leaf js_error_get_message AllocOnly js_error_get_name Reenters js_error_get_stack Reenters -js_error_is_error Leaf +js_error_is_error Reenters js_error_new Reenters js_error_new_from_value Reenters js_error_new_kind_from_value Reenters @@ -1102,6 +1104,7 @@ js_event_target_new Reenters js_event_target_remove_event_listener Reenters js_event_target_remove_event_listener_with_options Reenters js_event_target_set_max_listeners Reenters +js_event_target_subclass_init Reenters js_events_add_abort_listener Reenters js_events_get_event_listeners Reenters js_events_get_max_listeners Reenters @@ -1456,7 +1459,7 @@ js_import_meta_resolve Reenters js_import_meta_resolve_value Reenters js_in_operator Reenters js_in_operator_presence_ic Reenters -js_inherited_read_cache_hit_f64 Reenters +js_inherited_read_cache_hit_f64 Leaf js_inherited_read_cache_stats Leaf js_inline_arena_slow_alloc AllocOnly js_inline_arena_state Leaf @@ -2180,6 +2183,7 @@ js_object_set_symbol_method Reenters js_object_set_symbol_property Reenters js_object_shape_id_for_class_keys Reenters js_object_shape_id_for_class_keys_live Reenters +js_object_shape_id_for_class_keys_static Reenters js_object_shape_id_for_keys Reenters js_object_super_call Reenters js_object_super_get Reenters @@ -2716,6 +2720,8 @@ js_regexp_test Reenters js_regexp_to_string Reenters js_region_guard_pack Reenters js_region_guard_prime Reenters +js_region_loop_pack Leaf +js_region_loop_prime Leaf js_register_anon_shape_class_id Leaf js_register_aux_has_active Leaf js_register_aux_pump Leaf @@ -2743,6 +2749,7 @@ js_register_class_source_static Leaf js_register_class_static_getter Reenters js_register_class_static_method Reenters js_register_class_static_method_bind_length Leaf +js_register_class_static_method_entry Reenters js_register_class_static_setter Reenters js_register_class_string_member_order Leaf js_register_class_to_string_tag Leaf @@ -2784,7 +2791,6 @@ js_register_handle_property_set_dispatch Leaf js_register_handle_property_set_dispatch_extension Leaf js_register_handle_prototype_dispatch Leaf js_register_http_agent_handle_probe Leaf -js_register_imported_class_shape_slot Reenters js_register_jsx_render_stream Leaf js_register_net_socket_handle_probe Leaf js_register_node_stream_web_adapter_callbacks Leaf @@ -2974,6 +2980,9 @@ js_shadow_slot_get Leaf js_shadow_slot_set Leaf js_shadow_state_addr Leaf js_shape_ordinary_inline_slot_for_key Reenters +js_shape_register_static_seed Leaf +js_shape_run_static_seed Reenters +js_shape_seed_plain Reenters js_shared_array_buffer_new Reenters js_shared_array_buffer_new_value Reenters js_sharp_blur Reenters @@ -3017,9 +3026,12 @@ js_sqlite_stmt_get Reenters js_sqlite_stmt_raw Reenters js_sqlite_stmt_run Reenters js_sqlite_transaction Reenters +js_stack_overflow Leaf js_state_get Reenters js_state_init Reenters js_state_set Reenters +js_static_method_entry_enter Reenters +js_static_method_entry_leave Leaf js_static_this_arm_classref Reenters js_static_this_arm_value Leaf js_static_this_resolve Leaf @@ -3117,6 +3129,7 @@ js_string_normalize Reenters js_string_pad_end Reenters js_string_pad_fill Reenters js_string_pad_start Reenters +js_string_pool_atom Reenters js_string_position_to_index Leaf js_string_print Leaf js_string_raw Reenters @@ -3600,7 +3613,7 @@ js_v8_get_heap_statistics Reenters js_v8_is_building_snapshot Leaf js_v8_namespace Reenters js_v8_noop_undefined Leaf -js_v8_promise_hook_register Reenters +js_v8_promise_hook_register AllocOnly js_v8_promise_hooks_create_hook Reenters js_v8_promise_hooks_on_after Reenters js_v8_promise_hooks_on_before Reenters diff --git a/crates/perry-runtime/src/symbol.rs b/crates/perry-runtime/src/symbol.rs index 22997d8fb3..1bee0f9857 100644 --- a/crates/perry-runtime/src/symbol.rs +++ b/crates/perry-runtime/src/symbol.rs @@ -1032,6 +1032,20 @@ pub fn class_static_symbol_owner_for_test(class_id: u32) -> usize { crate::object::class_value::class_value_ptr(class_id) as usize } +/// Test probe (#11696): is `sym` an own key of the ordinary object `owner`'s +/// shape (#11682 stores those on the object, not in `SYMBOL_PROPERTIES`)? +/// `Some(is_accessor)` when it is, `None` when `owner` is not an ordinary +/// object or has no such key. +#[doc(hidden)] +pub fn symbol_on_object_for_test(owner: usize, sym: usize) -> Option { + // SAFETY: `owner` validates the address as a tracked GC object first. + unsafe { + crate::object::shaped_symbols::owner(owner)?; + crate::object::shaped_symbols::entry(owner, sym) + .map(|entry| entry & crate::object::key_attrs::ENTRY_ACCESSOR != 0) + } +} + // Monotonic id counter for fresh symbols. Not thread-safe per-thread but // Symbol semantics are compatible with coarse locking. static NEXT_SYMBOL_ID: Mutex = Mutex::new(1); diff --git a/crates/perry-stdlib/src/runtime_thread_exit_tests/symbols_tests.rs b/crates/perry-stdlib/src/runtime_thread_exit_tests/symbols_tests.rs index 9df0fecb8f..bd27fef6a1 100644 --- a/crates/perry-stdlib/src/runtime_thread_exit_tests/symbols_tests.rs +++ b/crates/perry-stdlib/src/runtime_thread_exit_tests/symbols_tests.rs @@ -52,106 +52,158 @@ fn addr_of(value: f64) -> usize { (value.to_bits() & ADDR_MASK) as usize } +/// The three own-symbol-property writes the side tables used to receive for +/// every owner: `o[sym] = v` (value), `Object.defineProperty(o, sym2, +/// { value: v, writable: false })` (attrs) and `Object.defineProperty(o, sym3, +/// { get })` (accessor). +fn define_three_symbol_properties( + scope: &RuntimeHandleScope, + owner: f64, + syms: [f64; 3], + value: f64, +) { + use perry_runtime::symbol as s; + unsafe { s::js_object_set_symbol_property(owner, syms[0], value) }; + let desc = scope.root_raw_mut_ptr(perry_runtime::object::js_object_alloc(0, 0)); + perry_runtime::js_object_set_field_by_name(desc.get_raw_mut_ptr(), key("value"), value); + perry_runtime::js_object_set_field_by_name( + desc.get_raw_mut_ptr(), + key("writable"), + f64::from_bits(TAG_FALSE), + ); + perry_runtime::object::js_object_define_property( + owner, + syms[1], + js_nanbox_pointer(desc.get_raw_mut_ptr::() as i64), + ); + let getter = scope.root_raw_mut_ptr(perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(probe_thunk, 0), + 0, + )); + let accessor = scope.root_raw_mut_ptr(perry_runtime::object::js_object_alloc(0, 0)); + perry_runtime::js_object_set_field_by_name( + accessor.get_raw_mut_ptr(), + key("get"), + js_nanbox_pointer(getter.get_raw_mut_ptr::() as i64), + ); + perry_runtime::object::js_object_define_property( + owner, + syms[2], + js_nanbox_pointer(accessor.get_raw_mut_ptr::() as i64), + ); +} + +/// What the address-keyed tables hold for `(owner, syms)`: the value record, +/// the attrs entry and the accessor entry. +fn side_tables_hold(owner: usize, syms: [usize; 3]) -> [bool; 3] { + use perry_runtime::symbol as s; + [ + s::symbol_property_tables_hold_for_test(owner, syms[0]).0, + s::symbol_property_tables_hold_for_test(owner, syms[1]).1, + s::symbol_accessor_held_for_test(owner, syms[2]), + ] +} + +/// #11471 / #11696. Since #11682 an ordinary object's symbol properties live +/// on the object itself (its shape's keys and its slots), so they die with +/// the thread's heap and the address-keyed side tables never see them. Owners +/// that are not ordinary objects (arrays here, and a class's static symbol +/// members) still use `SYMBOL_PROPERTIES` / `SYMBOL_PROPERTY_ATTRS` / +/// `SYMBOL_ACCESSOR_PROPERTIES`, and a dead thread's entries there must be +/// released at thread exit. The test proves both halves are live: the table +/// entries exist while the thread lives (and the ordinary object's are on the +/// object, NOT in the tables), and the table entries are gone after `join`. #[test] fn thread_exit_releases_the_threads_symbol_side_table_entries() { const STATIC_SYMBOL_CLASS: u32 = 0x0B11_4711; - let ((owner, class_owner, sym), alive) = std::thread::spawn(|| { - use perry_runtime::symbol as s; - let scope = RuntimeHandleScope::new(); - let sym = scope.root_nanbox_f64(unsafe { s::js_symbol_new(string_value("t11471")) }); - let obj = scope.root_raw_mut_ptr(perry_runtime::object::js_object_alloc(0, 0)); - let obj_value = || js_nanbox_pointer(obj.get_raw_mut_ptr::() as i64); - let value = scope.root_raw_mut_ptr(perry_runtime::js_array_alloc(0)); - let value_value = || js_nanbox_pointer(value.get_raw_mut_ptr::() as i64); - // obj[sym] = [] (SYMBOL_PROPERTIES). - unsafe { - s::js_object_set_symbol_property(obj_value(), sym.get_nanbox_f64(), value_value()) - }; - // Object.defineProperty(obj, sym2, { value: [], writable: false }) - // (SYMBOL_PROPERTY_ATTRS). - let sym2 = scope.root_nanbox_f64(unsafe { s::js_symbol_new(string_value("t11471b")) }); - let desc = scope.root_raw_mut_ptr(perry_runtime::object::js_object_alloc(0, 0)); - perry_runtime::js_object_set_field_by_name( - desc.get_raw_mut_ptr(), - key("value"), - value_value(), - ); - perry_runtime::js_object_set_field_by_name( - desc.get_raw_mut_ptr(), - key("writable"), - f64::from_bits(TAG_FALSE), - ); - perry_runtime::object::js_object_define_property( - obj_value(), - sym2.get_nanbox_f64(), - js_nanbox_pointer(desc.get_raw_mut_ptr::() as i64), - ); - // Object.defineProperty(obj, sym3, { get }) (SYMBOL_ACCESSOR_PROPERTIES). - let sym3 = scope.root_nanbox_f64(unsafe { s::js_symbol_new(string_value("t11471c")) }); - let getter = scope.root_raw_mut_ptr(perry_runtime::closure::js_closure_alloc( - perry_runtime::fn_info!(probe_thunk, 0), - 0, - )); - let accessor = scope.root_raw_mut_ptr(perry_runtime::object::js_object_alloc(0, 0)); - perry_runtime::js_object_set_field_by_name( - accessor.get_raw_mut_ptr(), - key("get"), - js_nanbox_pointer(getter.get_raw_mut_ptr::() as i64), - ); - perry_runtime::object::js_object_define_property( - obj_value(), - sym3.get_nanbox_f64(), - js_nanbox_pointer(accessor.get_raw_mut_ptr::() as i64), - ); - // static [sym] = [] on a class id: an own symbol property of the class's - // function object, which this thread's agent mints in its own heap. - unsafe { - s::js_class_register_static_symbol( - STATIC_SYMBOL_CLASS, - sym.get_nanbox_f64(), - value_value(), - ) - }; + let ((holder, class_owner, obj, syms), alive, on_object, obj_in_tables) = + std::thread::spawn(|| { + use perry_runtime::symbol as s; + let scope = RuntimeHandleScope::new(); + let sym = scope.root_nanbox_f64(unsafe { s::js_symbol_new(string_value("t11471")) }); + let sym2 = scope.root_nanbox_f64(unsafe { s::js_symbol_new(string_value("t11471b")) }); + let sym3 = scope.root_nanbox_f64(unsafe { s::js_symbol_new(string_value("t11471c")) }); + let syms = || { + [ + sym.get_nanbox_f64(), + sym2.get_nanbox_f64(), + sym3.get_nanbox_f64(), + ] + }; + let value = scope.root_raw_mut_ptr(perry_runtime::js_array_alloc(0)); + let value_value = || js_nanbox_pointer(value.get_raw_mut_ptr::() as i64); + // A table-backed owner: an array is not an ordinary object. + let holder = scope.root_raw_mut_ptr(perry_runtime::js_array_alloc(0)); + let holder_value = || js_nanbox_pointer(holder.get_raw_mut_ptr::() as i64); + define_three_symbol_properties(&scope, holder_value(), syms(), value_value()); + // An ordinary object: the same writes land on the object. + let obj = scope.root_raw_mut_ptr(perry_runtime::object::js_object_alloc(0, 0)); + let obj_value = || js_nanbox_pointer(obj.get_raw_mut_ptr::() as i64); + define_three_symbol_properties(&scope, obj_value(), syms(), value_value()); + // static [sym] = [] on a class id: an own symbol property of the + // class's function object, which this thread's agent mints in its + // own heap, still kept in `SYMBOL_PROPERTIES`. + unsafe { + s::js_class_register_static_symbol(STATIC_SYMBOL_CLASS, syms()[0], value_value()) + }; + let read_back = unsafe { s::js_object_get_symbol_property(obj_value(), syms()[0]) }; + assert_eq!( + read_back.to_bits(), + value_value().to_bits(), + "obj[sym] must read back the stored value" + ); - let owner = obj.get_raw_mut_ptr::() as usize; - let class_owner = s::class_static_symbol_owner_for_test(STATIC_SYMBOL_CLASS); - let (sym, sym2, sym3) = ( - addr_of(sym.get_nanbox_f64()), - addr_of(sym2.get_nanbox_f64()), - addr_of(sym3.get_nanbox_f64()), - ); - let alive = [ - s::symbol_property_tables_hold_for_test(owner, sym).0, - s::symbol_property_tables_hold_for_test(owner, sym2).1, - s::symbol_accessor_held_for_test(owner, sym3), - s::symbol_property_tables_hold_for_test(class_owner, sym).0, - ]; - ((owner, class_owner, [sym, sym2, sym3]), alive) - }) - .join() - .unwrap(); + let holder = holder.get_raw_mut_ptr::() as usize; + let obj = obj.get_raw_mut_ptr::() as usize; + let class_owner = s::class_static_symbol_owner_for_test(STATIC_SYMBOL_CLASS); + let syms = syms().map(addr_of); + let held = side_tables_hold(holder, syms); + let alive = [ + held[0], + held[1], + held[2], + s::symbol_property_tables_hold_for_test(class_owner, syms[0]).0, + ]; + let on_object = syms.map(|sym| s::symbol_on_object_for_test(obj, sym)); + let obj_in_tables = side_tables_hold(obj, syms); + ( + (holder, class_owner, obj, syms), + alive, + on_object, + obj_in_tables, + ) + }) + .join() + .unwrap(); use perry_runtime::symbol as s; assert_eq!( alive, [true; 4], - "every entry must exist while its thread lives" + "every table entry must exist while its thread lives" ); - assert!( - !s::symbol_property_tables_hold_for_test(owner, sym[0]).0, - "a dead thread's obj[sym] record outlived its heap" + assert_eq!( + on_object, + [Some(false), Some(false), Some(true)], + "an ordinary object's symbol value, attrs and accessor live on the object" ); - assert!( - !s::symbol_property_tables_hold_for_test(owner, sym[1]).1, - "a dead thread's symbol property attrs outlived its heap" + assert_eq!( + obj_in_tables, [false; 3], + "an ordinary object's symbol properties must not also be in the side tables" ); - assert!( - !s::symbol_accessor_held_for_test(owner, sym[2]), - "a dead thread's symbol accessor outlived its heap" + assert_eq!( + side_tables_hold(holder, syms), + [false; 3], + "a dead thread's holder[sym] record / symbol attrs / symbol accessor outlived its heap" ); assert!( - !s::symbol_property_tables_hold_for_test(class_owner, sym[0]).0, + !s::symbol_property_tables_hold_for_test(class_owner, syms[0]).0, "a dead thread's class-static symbol member outlived its heap" ); + assert_eq!( + side_tables_hold(obj, syms), + [false; 3], + "a dead thread's ordinary object gained side-table symbol entries" + ); } /// Is the symbol `(addr, id)` still in `SYMBOL_POINTERS`?