From ab0b874281901c1d045e6c51ea5f775ed008d6b2 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 2 Oct 2026 16:55:04 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=99=8B=E5=8D=87=E6=97=8F?= =?UTF-8?q?=E6=95=B4=E6=96=87=E4=BB=B6=E9=80=80=E5=BD=B9=E5=8A=A0=E7=94=9F?= =?UTF-8?q?=E4=BA=A7=E8=B0=83=E7=94=A8=E7=82=B9=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_cli/src/bench/js_stress.rs | 7 +- crates/oxide_vm/src/async_func.rs | 2 + crates/oxide_vm/src/async_generator.rs | 2 + crates/oxide_vm/src/generator.rs | 2 + crates/oxide_vm/src/lib.rs | 1 - crates/oxide_vm/src/promise/gc_edges.rs | 4 + crates/oxide_vm/src/promise/jobs.rs | 1 + crates/oxide_vm/src/promise/mod.rs | 6 +- crates/oxide_vm/src/session_arena.rs | 524 ------------------ crates/oxide_vm/src/session_gc.rs | 7 +- crates/oxide_vm/src/session_gc/tests.rs | 108 ++-- crates/oxide_vm/src/suspended.rs | 2 + crates/oxide_vm/src/vm/gc_hooks.rs | 1 + crates/oxide_vm/src/vm/vmhost.rs | 4 +- crates/oxide_vm/src/vm_runtime.rs | 4 +- crates/oxide_vm/src/vm_support.rs | 8 +- .../tests/object_graph_retention_tests.rs | 23 +- 17 files changed, 93 insertions(+), 613 deletions(-) delete mode 100644 crates/oxide_vm/src/session_arena.rs diff --git a/crates/oxide_cli/src/bench/js_stress.rs b/crates/oxide_cli/src/bench/js_stress.rs index 25358405..5834e09e 100644 --- a/crates/oxide_cli/src/bench/js_stress.rs +++ b/crates/oxide_cli/src/bench/js_stress.rs @@ -108,12 +108,7 @@ pub fn run_js_stress_bench(config: &BenchConfig, kernel: &Arc, pool: let ic_hits = vm.ic_hit_count(); let ic_misses = vm.ic_miss_count(); - // 留存口径:先让存活集对 session 完全可见——根直接持有的 epoch 对象 - // (顶层 var 等)晋升进 session,session 对象持有的 epoch 子引用 - // (闭包捕获、原生盒直插)同样晋升(均与 epoch 边界同机制); - // 再强制完整 GC 清掉不可达垃圾,清扫后账目即 workload 留存堆 - vm.promote_rooted_epoch_objects(); - vm.promote_session_epoch_refs(); + // 留存口径:强制完整 GC 清掉不可达垃圾后账目即留存堆。 vm.collect_session_gc(); let retained_bytes = vm.session_bytes_allocated(); let retained_objects = vm.session_object_count(); diff --git a/crates/oxide_vm/src/async_func.rs b/crates/oxide_vm/src/async_func.rs index a926f1ed..31e26ce5 100644 --- a/crates/oxide_vm/src/async_func.rs +++ b/crates/oxide_vm/src/async_func.rs @@ -765,6 +765,7 @@ pub(crate) fn async_native_cell_edges(obj: &JsObject) -> Vec<*mut Cell> { } /// 用转发函数重写状态快照中的所有 JsValue(session GC 移动式清扫 / promote 用)。 +#[allow(dead_code)] pub(crate) fn rewrite_async_native(obj: &JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue) { let Some(state) = async_state_mut(obj) else { return; @@ -781,6 +782,7 @@ pub(crate) fn rewrite_async_native(obj: &JsObject, mut rewrite: impl FnMut(JsVal } /// 深拷贝状态盒到新对象(promote / sweep 用):新对象持独立 Box,源盒可安全释放。 +#[allow(dead_code)] pub(crate) fn clone_async_native_with_rewrite( old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue, ) { diff --git a/crates/oxide_vm/src/async_generator.rs b/crates/oxide_vm/src/async_generator.rs index be769b18..de1c7a6e 100644 --- a/crates/oxide_vm/src/async_generator.rs +++ b/crates/oxide_vm/src/async_generator.rs @@ -1753,6 +1753,7 @@ pub(crate) fn async_generator_native_cell_edges(obj: &JsObject) -> Vec<*mut Cell } /// 用转发函数重写状态快照中的所有 JsValue(session GC 移动式清扫 / promote 用)。 +#[allow(dead_code)] pub(crate) fn rewrite_async_generator_native(obj: &JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue) { let Some(state) = async_gen_state_mut(obj) else { return; @@ -1776,6 +1777,7 @@ pub(crate) fn rewrite_async_generator_native(obj: &JsObject, mut rewrite: impl F } /// 深拷贝状态盒到新对象(promote / sweep 用):新对象持独立 Box,源盒可安全释放。 +#[allow(dead_code)] pub(crate) fn clone_async_generator_native_with_rewrite( old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue, ) { diff --git a/crates/oxide_vm/src/generator.rs b/crates/oxide_vm/src/generator.rs index 8969124e..c6cc10d7 100644 --- a/crates/oxide_vm/src/generator.rs +++ b/crates/oxide_vm/src/generator.rs @@ -1023,6 +1023,7 @@ pub(crate) fn generator_native_cell_edges(obj: &JsObject) -> Vec<*mut Cell> { } /// 用转发函数重写状态快照中的所有 JsValue(promote 用)。 +#[allow(dead_code)] pub(crate) fn rewrite_generator_native(obj: &JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue) { let Some(state) = generator_state_mut(obj) else { return; @@ -1036,6 +1037,7 @@ pub(crate) fn rewrite_generator_native(obj: &JsObject, mut rewrite: impl FnMut(J } /// 深拷贝状态盒到新对象(promote / sweep 用):新对象持独立 Box,源盒可安全释放。 +#[allow(dead_code)] pub(crate) fn clone_generator_native_with_rewrite( old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue, ) { diff --git a/crates/oxide_vm/src/lib.rs b/crates/oxide_vm/src/lib.rs index 9ec6a1b9..2eb7165d 100644 --- a/crates/oxide_vm/src/lib.rs +++ b/crates/oxide_vm/src/lib.rs @@ -23,7 +23,6 @@ pub mod native; mod native_box_dispatch; /// Promise 运行时(状态盒 + 微任务队列 + 构造器/方法实现)。 pub mod promise; -mod session_arena; /// session 级 mark-sweep GC:标记-清扫 session arena 对象与 session 字符串。 pub mod session_gc; mod suspended; diff --git a/crates/oxide_vm/src/promise/gc_edges.rs b/crates/oxide_vm/src/promise/gc_edges.rs index 4611c43c..d2f65172 100644 --- a/crates/oxide_vm/src/promise/gc_edges.rs +++ b/crates/oxide_vm/src/promise/gc_edges.rs @@ -38,6 +38,7 @@ pub(crate) fn promise_native_edges(obj: &JsObject) -> Vec { /// 结算链指针是裸指针边、不在本函数改写面:晋升克隆新分配于 session arena /// (原件地址不变),改写场景指针天然有效;结算链接链由 /// `migrate_settlement_to_newest_clone` 负责。 +#[allow(dead_code)] pub(crate) fn rewrite_promise_native(obj: &JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue) { let Some(state) = promise_state_mut(obj) else { return; @@ -59,6 +60,7 @@ pub(crate) fn rewrite_promise_native(obj: &JsObject, mut rewrite: impl FnMut(JsV /// 永久丢失、原件侧消费者永不触发;每条反应的 JsValue 随状态盒其余字段同一 /// pass 晋升/改写。结算链指针按原值保留(接链由 /// `migrate_settlement_to_newest_clone` 负责)。 +#[allow(dead_code)] pub(crate) fn clone_promise_native_with_rewrite( old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue, ) { @@ -110,6 +112,7 @@ pub(crate) fn clone_promise_native_with_rewrite( /// `clone_promise_native_with_rewrite`);session 搬移(GC sweep)不改结算 /// 拓扑,不得调用。 /// - 源非 pending 时无事可做直接返回(已结算原件不再传导)。 +#[allow(dead_code)] pub(crate) fn migrate_settlement_to_newest_clone( old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue, ) { @@ -146,6 +149,7 @@ pub(crate) fn migrate_settlement_to_newest_clone( /// 改写单条反应的四条 JsValue 引用(派生 promise 能力与处理器),源反应迁移 /// 与再晋升合并共用。 +#[allow(dead_code)] fn rewrite_reaction JsValue>(r: PromiseReaction, rewrite: &mut F) -> PromiseReaction { PromiseReaction { promise: rewrite(r.promise), diff --git a/crates/oxide_vm/src/promise/jobs.rs b/crates/oxide_vm/src/promise/jobs.rs index fa53c28b..574d57cb 100644 --- a/crates/oxide_vm/src/promise/jobs.rs +++ b/crates/oxide_vm/src/promise/jobs.rs @@ -109,6 +109,7 @@ pub(crate) fn for_each_job_value(job: &Microtask, mut f: impl FnMut(JsValue)) { } /// 供外部改写微任务队列中的 JsValue(session GC sweep 用)。 +#[allow(dead_code)] pub(crate) fn rewrite_job_values(job: &mut Microtask, mut rewrite: impl FnMut(JsValue) -> JsValue) { match job { Microtask::Reaction { diff --git a/crates/oxide_vm/src/promise/mod.rs b/crates/oxide_vm/src/promise/mod.rs index 2c62ae28..ce8ef4d1 100644 --- a/crates/oxide_vm/src/promise/mod.rs +++ b/crates/oxide_vm/src/promise/mod.rs @@ -17,10 +17,7 @@ mod jobs; mod reactions; mod settlement; -pub(crate) use gc_edges::{ - clone_promise_native_with_rewrite, drop_promise_native, migrate_settlement_to_newest_clone, promise_native_edges, - promise_native_size, rewrite_promise_native, -}; +pub(crate) use gc_edges::{drop_promise_native, promise_native_edges, promise_native_size}; pub(crate) use jobs::{for_each_job_value, rewrite_job_values}; pub use reactions::promise_settled_value; @@ -161,6 +158,7 @@ fn promise_state_ref(obj: &JsObject) -> Option<&PromiseState> { } #[expect(clippy::mut_from_ref)] +#[allow(dead_code)] fn promise_state_mut(obj: &JsObject) -> Option<&mut PromiseState> { let ptr = obj.native_data() as *mut PromiseState; if ptr.is_null() { diff --git a/crates/oxide_vm/src/session_arena.rs b/crates/oxide_vm/src/session_arena.rs deleted file mode 100644 index 679f2830..00000000 --- a/crates/oxide_vm/src/session_arena.rs +++ /dev/null @@ -1,524 +0,0 @@ -use std::collections::HashMap; - -use crate::vm_debug; -use oxide_types::object::JsObject; -use oxide_types::value::JsValue; -use rustc_hash::FxBuildHasher; - -use crate::vm::Vm; -use oxide_builtins::{array_buffer, data_view, disposable_stack, map, module, regexp, set, typed_array, weak_map}; - -impl Vm { - /// 单对象晋升测试入口:局部转发表下调 `promote_object_inner`。 - /// 生产路径(边界修复/根晋升)各自持表管理,不经过此包装。 - #[cfg(test)] - pub(crate) fn promote_object(&mut self, src: *mut JsObject) -> *mut JsObject { - vm_debug!("promote_object: src={:p}", src); - let mut forwarding = HashMap::with_hasher(FxBuildHasher); - let result = self.promote_object_inner(src, &mut forwarding); - // 局部表随作用域丢弃,晋升克隆的转发改写已在其上完成。 - forwarding.clear(); - result - } - - pub(crate) fn promote_object_inner( - &mut self, src: *mut JsObject, forwarding: &mut HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>, - ) -> *mut JsObject { - vm_debug!("promote_object_inner: src={:p}", src); - if src.is_null() { - return src; - } - let src_ref = unsafe { &*src }; - if src_ref.is_session_epoch() || !src_ref.is_epoch() { - return src; - } - if let Some(dst) = forwarding.get(&src).copied() { - return dst; - } - - let clone = src_ref.clone_for_session_epoch(); - let dst = self.gc_state.session_epoch.alloc(clone) as *mut JsObject; - forwarding.insert(src, dst); - self.gc_state.session_object_ptrs.push(dst); - - let dst_ref = unsafe { &mut *dst }; - dst_ref.rewrite_object_values(|value| self.promote_value_if_epoch_object(value, forwarding)); - if src_ref.is_map() { - map::clone_map_native_with_rewrite(src_ref, dst_ref, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if src_ref.is_set() { - set::clone_set_native_with_rewrite(src_ref, dst_ref, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if src_ref.is_weak_map_obj() { - // 值边随克隆晋升(强边);弱键原样搬运,生死交晋升收敛后的 - // 弱键定夺路径(现为原样保留)。 - weak_map::clone_weak_map_native_with_rewrite(src_ref, dst_ref, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if src_ref.is_disposable_stack_obj() || src_ref.is_async_disposable_stack_obj() { - // 资源栈状态盒深拷贝到新对象:源盒随 epoch 释放,互不共享。 - disposable_stack::clone_dispose_native_with_rewrite(src_ref, dst_ref, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if src_ref.is_typed_array_obj() { - typed_array::clone_typed_array_native_with_rewrite(src_ref, dst_ref, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if src_ref.is_data_view_obj() { - data_view::clone_data_view_native_with_rewrite(src_ref, dst_ref, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if src_ref.is_array_buffer_obj() { - // 字节缓冲深拷贝到新对象:源 Vec 随 epoch 释放,互不共享。 - array_buffer::clone_array_buffer_native(src_ref, dst_ref); - } else if src_ref.is_shared_array_buffer_obj() { - // 字节缓冲深拷贝到新对象:源 Vec 随 epoch 释放,互不共享。 - array_buffer::clone_shared_array_buffer_native(src_ref, dst_ref); - } else if src_ref.is_generator_obj() { - // 生成器状态盒深拷贝到新对象:源盒随 epoch 释放,互不共享。 - crate::generator::clone_generator_native_with_rewrite(src_ref, dst_ref, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if src_ref.is_promise_obj() { - // Promise 状态盒深拷贝到新对象:源盒随 epoch 释放,互不共享; - // 反应迁入新克隆并接结算链(原件/旧克隆结算沿链传导到最新克隆)。 - crate::promise::clone_promise_native_with_rewrite(src_ref, dst_ref, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - crate::promise::migrate_settlement_to_newest_clone(src_ref, dst_ref, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if src_ref.is_async_obj() { - // 异步状态盒深拷贝到新对象:源盒随 epoch 释放,互不共享。 - crate::async_func::clone_async_native_with_rewrite(src_ref, dst_ref, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if src_ref.is_async_generator_obj() { - // 异步生成器状态盒深拷贝到新对象:源盒随 epoch 释放,互不共享。 - crate::async_generator::clone_async_generator_native_with_rewrite(src_ref, dst_ref, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if src_ref.holds_compiled_regex() { - // 已编译正则是 Box 深拷贝到新对象:源盒随 epoch 释放,互不共享。 - regexp::clone_regexp_native(src_ref, dst_ref); - } else if src_ref.is_module_namespace() { - // 条目表深拷贝到新对象并改写值边:`clone_for_session_epoch` 只浅拷贝 - // native_data 指针,共享同一表会在释放时双放。 - module::clone_module_ns_native_with_rewrite(src_ref, dst_ref, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } - // 账目计入:对象头 + 堆数据(属性/元素/meta Vec + native 状态盒),用 clone 后的 dst 核算。 - self.gc_state.session_bytes_allocated += - std::mem::size_of::() + crate::session_gc::SessionGc::object_heap_data_bytes(dst_ref) as usize; - dst - } - - /// 克隆改写与 reset 边界晋升共用的单引用改写:epoch 对象克隆晋升进 - /// session(forwarding 去重共享与环);其余值原样保留。 - pub(crate) fn promote_value_if_epoch_object( - &mut self, value: JsValue, forwarding: &mut HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>, - ) -> JsValue { - if !value.is_object() { - return value; - } - let ptr = value.as_js_object_ptr(); - if ptr.is_null() { - return value; - } - // SAFETY: 执行核心产出的对象值,指针在 session 生命周期内有效。 - if unsafe { &*ptr }.is_epoch() { - return JsValue::from_js_object(self.promote_object_inner(ptr, forwarding)); - } - value - } - - /// epoch 边界前的 session 原地晋升:遍历全部 session 对象,把仍指向 epoch - /// 对象的引用克隆进 session(JS 边 + native 状态盒),消除 epoch 重置后的 - /// 悬垂指针。 - /// - /// 覆盖三类绕过写屏障的引用来源:函数对象直接 session 分配后捕获的 - /// `captured_this`/`home_object`/upvalue cell/属性值(SET_HOME_OBJECT 与 - /// 函数目标豁免均直落 epoch 值);Map/Set 等原生盒按键值直插 epoch 值; - /// global 对象的属性值(逃逸写直通后 epoch 值直落 global 槽,global 不入 - /// session 对象表,由 `rewrite_session_epoch_refs` 同趟改写)。 - /// `rewrite_object_values` 覆盖元素/meta/属性/proto/captured_this/home_object/ - /// cell 值;克隆子树经局部转发表去重,环与共享引用各克隆一次。 - /// 统一入口 Box 化后生产路径不产 epoch 对象,本方法为晋升族空转保留面。 - /// - /// # 注意事项 - /// - 须在执行外的安全点调用(无在途 builtin 局部裸指针、dispatch 未重入): - /// 本方法原地改写 session 对象的引用字段,执行期调用将使 builtin 局部 - /// 裸指针失效(与 `collect_session_gc` 同前提)。 - /// - session 对象表为空时 global 的 epoch 子引用仍须修复,不做空表早退。 - pub fn promote_session_epoch_refs(&mut self) { - let objects = std::mem::take(&mut self.gc_state.session_object_ptrs); - let mut forwarding = HashMap::with_hasher(FxBuildHasher); - self.rewrite_session_epoch_refs(&objects, &mut forwarding); - // 弱键定夺退化为原样保留(定夺路径无克隆可重指,见 - // resolve_weak_key_after_promotion);调用保留以收口族内流程。 - self.rewrite_weak_map_keys_after_promotion(&objects, &mut forwarding); - // 局部转发表随作用域丢弃:晋升新克隆已推入对象表,旧对象按原序拼回。 - self.gc_state.session_object_ptrs.extend(objects); - } - - /// 按调用方给定的转发表把对象列表的 epoch 子引用就地改写:未在上游晋升过的 - /// epoch 子对象经转发表克隆进 session(递归去重共享与环),已是 session/ - /// 非 epoch 的值原样保留。JS 边与 native 状态盒走同一改写闭包。 - /// - /// global 对象不入 session 对象表(P 根,不在 arena 内):其 epoch 子引用 - /// 与列表同趟改写,无 native 盒只覆盖 JS 边。 - /// - /// # 注意事项 - /// - 调用方持有局部转发表期间,晋升新克隆推入的对象表由调用方决定何时 - /// 拼回;本方法不改表。 - /// - 死对象(不可达)的 epoch 子引用改写会把死对象一并克隆进 session—— - /// 克隆体无根,下一轮收集按死对象出表,不泄漏也不悬垂。 - pub(crate) fn rewrite_session_epoch_refs( - &mut self, objects: &[*mut JsObject], forwarding: &mut HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>, - ) { - for &ptr in objects { - if ptr.is_null() { - continue; - } - // SAFETY: ptr 来自 session_epoch.alloc,arena 存活期内有效。 - unsafe { - let obj = &mut *ptr; - obj.rewrite_object_values(|value| self.promote_value_if_epoch_object(value, forwarding)); - if obj.is_map() { - map::rewrite_map_native(obj, |value| self.promote_value_if_epoch_object(value, forwarding)); - } else if obj.is_set() { - set::rewrite_set_native(obj, |value| self.promote_value_if_epoch_object(value, forwarding)); - } else if obj.is_weak_map_obj() { - // 值边同强边改写;弱键留给转发表收敛后的定夺路径, - // 提前改写会引入晋升序依赖(键的强可达根尚未遍历完)。 - weak_map::rewrite_weak_map_native_values(obj, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if obj.is_disposable_stack_obj() || obj.is_async_disposable_stack_obj() { - disposable_stack::rewrite_dispose_native(obj, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if obj.is_typed_array_obj() { - typed_array::rewrite_typed_array_native(obj, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if obj.is_data_view_obj() { - data_view::rewrite_data_view_native(obj, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if obj.is_generator_obj() { - crate::generator::rewrite_generator_native(obj, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if obj.is_promise_obj() { - crate::promise::rewrite_promise_native(obj, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if obj.is_async_obj() { - crate::async_func::rewrite_async_native(obj, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if obj.is_async_generator_obj() { - crate::async_generator::rewrite_async_generator_native(obj, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } else if obj.is_module_namespace() { - module::rewrite_module_ns_native(obj, |value| { - self.promote_value_if_epoch_object(value, forwarding) - }); - } - } - } - // global 对象不入 session 对象表:其 epoch 子引用(逃逸写直通留存、 - // 函数目标豁免、native 盒直插)与列表同趟修复,免 epoch 释放后悬垂。 - let global_ptr = self.session.global_object().as_ptr() as *mut JsObject; - // SAFETY: global 归本 session 所有,安全点内指针有效。 - unsafe { - let obj = &mut *global_ptr; - obj.rewrite_object_values(|value| self.promote_value_if_epoch_object(value, forwarding)); - } - } - - /// 晋升收敛后的弱键定夺:对候选集(session 对象列表 + 已晋升克隆)中的 - /// 每个弱表按定夺路径处理条目。统一入口 Box 化后定夺退化为原样保留 - /// (无克隆可重指、reset 边界 mark 位无本轮可达性信息),本路径不改表项。 - /// - /// # 副作用 - /// - 每个弱表原位重建条目表(整表重建口径,条目内容不变)。 - /// - /// # 边界与前提 - /// - 须在主改写路径(JS 边 + 值边)对转发表收敛完成后调用;转发表届时 - /// 即最终强可达集(统一入口树上恒空)。调用方负责其后的清表。 - /// - global 上的弱表经其晋升克隆进入转发表值集,候选集无需单列 global。 - pub(crate) fn rewrite_weak_map_keys_after_promotion( - &mut self, objects: &[*mut JsObject], forwarding: &mut HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>, - ) { - for &ptr in objects { - if ptr.is_null() { - continue; - } - // SAFETY: session 对象在 arena 存活期内有效。 - unsafe { - let obj = &mut *ptr; - if obj.is_weak_map_obj() { - weak_map::rewrite_weak_map_native( - obj, - crate::session_gc::resolve_weak_key_after_promotion, - |value| value, - ); - } - } - } - for &new_ptr in forwarding.values() { - // SAFETY: 晋升克隆分配于 session arena,存活期内有效。 - unsafe { - let obj = &mut *new_ptr; - if obj.is_weak_map_obj() { - weak_map::rewrite_weak_map_native( - obj, - crate::session_gc::resolve_weak_key_after_promotion, - |value| value, - ); - } - } - } - } - - /// 把根直接持有的 epoch 对象(顶层 var 寄存器、挂起句柄等)晋升进 session, - /// 并把根引用改写到克隆体。统一入口 Box 化后生产路径无 epoch 对象, - /// 本方法为晋升族空转保留面(基准留存测量与图留存测试仍经此入口)。 - /// - /// # 副作用 - /// - 每个根 epoch 对象深克隆进 session(含 native 状态盒,子引用递归晋升), - /// 账目计入克隆字节;根引用按局部转发表重写。 - /// - /// # 注意事项 - /// - 须在执行外的安全点调用(无在途 builtin 局部裸指针、dispatch 未重入): - /// 本方法改写全部根寄存器槽,执行期调用将使 builtin 局部裸指针失效 - /// (与 `collect_session_gc` 同前提)。 - /// - 与 `promote_session_epoch_refs` 互补:本方法处理根直接持有的 epoch 对象, - /// 后者处理 session 对象持有的 epoch 子引用(闭包捕获等绕过写屏障的来源)。 - pub fn promote_rooted_epoch_objects(&mut self) { - let mut epoch_roots = Vec::new(); - self.for_each_root(|value| { - if value.is_object() { - let ptr = value.as_js_object_ptr(); - if !ptr.is_null() { - // SAFETY: 根上的对象值均为 VM 自有存活 JsObject。 - if unsafe { &*ptr }.is_epoch() { - epoch_roots.push(ptr); - } - } - } - }); - // 局部转发表:晋升克隆去重共享与环,作用域结束即弃。 - let mut forwarding = HashMap::with_hasher(FxBuildHasher); - for ptr in epoch_roots { - self.promote_object_inner(ptr, &mut forwarding); - } - crate::session_gc::rewrite_vm_roots(self, &forwarding); - // 弱表键定夺退化为原样保留(定夺路径无克隆可重指,见 - // resolve_weak_key_after_promotion);调用保留以收口族内流程。 - self.rewrite_weak_map_keys_after_promotion(&[], &mut forwarding); - } - - /// 逃逸写屏障:写向全局/session 目标的对象值原样直通。 - /// - /// session 对象持有 epoch 子引用是 GC 设计的一等存活态:epoch 边界 reset - /// 经 `promote_session_epoch_refs` 统一克隆晋升(转发表去重共享与环) - /// 覆盖全部跨界持有面;builtin 调用期内 epoch 不重置,帧局部裸指针 - /// 全程有效。此处若克隆,同一逻辑对象分裂为原件与克隆两份,经克隆的写 - /// 对方经原件的读不可见(builtin 帧指针与逃逸目标双引用并存时即暴露)。 - pub(crate) fn promote_if_needed_for_write_ptr(&mut self, _target_ptr: *mut JsObject, value: JsValue) -> JsValue { - value - } -} - -#[cfg(test)] -mod tests { - use super::*; - use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; - use oxide_types::object::{PropAttributes, PropMetaEntry}; - - fn plain_object(vm: &mut Vm) -> *mut JsObject { - let proto = vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject; - let ptr = vm - .epoch - .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto))); - // 测试辅助函数绕过 alloc_object,需手动置位 EPOCH_BIT。 - unsafe { (*ptr).set_is_epoch(true) }; - ptr - } - - fn is_epoch_object(_vm: &Vm, value: JsValue) -> bool { - value.is_object() && unsafe { (&*value.as_js_object_ptr()).is_epoch() } - } - - #[test] - fn session_arena_promotion_preserves_cycle_identity() { - let mut vm = Vm::new(); - let root = plain_object(&mut vm); - unsafe { - (*root).set_prop_at(0, JsValue::from_js_object(root)); - } - - let promoted = vm.promote_object(root); - let promoted_obj = unsafe { &*promoted }; - - assert!(promoted_obj.is_session_epoch()); - assert!(std::ptr::eq(promoted_obj.get_prop_at(0).as_js_object_ptr(), promoted)); - } - - #[test] - fn session_arena_promotion_preserves_duplicate_child_reference() { - let mut vm = Vm::new(); - let root = plain_object(&mut vm); - let child = plain_object(&mut vm); - unsafe { - (*root).set_prop_at(0, JsValue::from_js_object(child)); - (*root).set_prop_at(1, JsValue::from_js_object(child)); - } - - let promoted = vm.promote_object(root); - let promoted_obj = unsafe { &*promoted }; - let left = promoted_obj.get_prop_at(0).as_js_object_ptr(); - let right = promoted_obj.get_prop_at(1).as_js_object_ptr(); - - assert!(std::ptr::eq(left, right)); - assert!(!std::ptr::eq(left, child)); - assert!(unsafe { (&*left).is_session_epoch() }); - } - - #[test] - fn session_arena_captured_and_home_object_links_promote() { - let mut vm = Vm::new(); - let root = plain_object(&mut vm); - let proto = plain_object(&mut vm); - let captured = plain_object(&mut vm); - let home = plain_object(&mut vm); - let getter = plain_object(&mut vm); - let setter = plain_object(&mut vm); - unsafe { - (*root).set_proto(JsValue::from_js_object(proto)).expect("proto"); - (*root).set_captured_this(JsValue::from_js_object(captured)); - (*root).set_home_object(JsValue::from_js_object(home)); - (*root).set_prop_at(0, JsValue::undefined()); - (*root).set_accessor_meta( - 0, - JsValue::from_js_object(getter), - JsValue::from_js_object(setter), - PropAttributes::DEFAULT_DATA, - ); - } - - let promoted = vm.promote_object(root); - let promoted_obj = unsafe { &*promoted }; - let meta = promoted_obj.prop_meta_at(0).expect("meta"); - - assert!(!is_epoch_object(&vm, promoted_obj.proto())); - assert!(!is_epoch_object(&vm, promoted_obj.captured_this())); - assert!(!is_epoch_object(&vm, promoted_obj.home_object())); - assert!(!is_epoch_object(&vm, meta.get)); - assert!(!is_epoch_object(&vm, meta.set)); - } - - #[test] - fn session_arena_promotes_array_elements_and_element_meta() { - let mut vm = Vm::new(); - let elem_child = plain_object(&mut vm); - let meta_getter = plain_object(&mut vm); - let array_proto = vm.session.builtin_world().array_proto.as_ptr() as *mut JsObject; - let arr = vm.epoch.alloc(JsObject::new_array( - EMPTY_SHAPE_ID, - JsValue::from_js_object(array_proto), - 2, - vm.epoch.bump(), - )); - // 测试辅助函数绕过 alloc_object,需手动置位 EPOCH_BIT。 - unsafe { (*arr).set_is_epoch(true) }; - unsafe { - (*arr).set_prop_at(0, JsValue::from_js_object(elem_child)); - (*arr).set_accessor_meta( - 1, - JsValue::from_js_object(meta_getter), - JsValue::undefined(), - PropAttributes::DEFAULT_DATA, - ); - (*arr).set_prop_shape(0, JsValue::int(99)); - } - - let promoted = vm.promote_object(arr); - let promoted_arr = unsafe { &*promoted }; - - assert!(promoted_arr.is_session_epoch()); - // 数组元素(含访问器 get)与命名属性随 clone + rewrite 迁移到 session。 - assert!(!is_epoch_object(&vm, promoted_arr.get_prop_at(0))); - let meta = promoted_arr.prop_meta_at(1).expect("element accessor meta"); - assert!(!is_epoch_object(&vm, meta.get)); - assert_eq!(promoted_arr.get_prop_shape(0), JsValue::int(99)); - // 元素计数与长度不因 promotion 改变。 - assert_eq!(promoted_arr.prop_count(), 2); - } - - #[test] - fn session_arena_barrier_global_root_write_returns_original() { - let mut vm = Vm::new(); - let value = JsValue::from_js_object(plain_object(&mut vm)); - let global_ptr = vm.session.global_object().as_ptr() as *mut JsObject; - - let promoted = vm.promote_if_needed_for_write_ptr(global_ptr, value); - - assert_eq!(promoted, value); - assert!(is_epoch_object(&vm, promoted), "epoch 子引用应直通留存,由边界/晋升档统一修复"); - } - - #[test] - fn session_arena_barrier_session_target_write_returns_original() { - let mut vm = Vm::new(); - let target_epoch = plain_object(&mut vm); - let target = vm.promote_object(target_epoch); - let value = JsValue::from_js_object(plain_object(&mut vm)); - - let promoted = vm.promote_if_needed_for_write_ptr(target, value); - - assert_eq!(promoted, value); - assert!(is_epoch_object(&vm, promoted), "session 目标上的 epoch 值应直通留存"); - } - - #[test] - fn session_arena_barrier_leaves_non_escape_target_write_unchanged() { - let mut vm = Vm::new(); - let target = plain_object(&mut vm); - let value = JsValue::from_js_object(plain_object(&mut vm)); - - let unchanged = vm.promote_if_needed_for_write_ptr(target, value); - - assert_eq!(unchanged, value); - } - - #[test] - fn session_arena_promotion_rewrites_accessor_metadata_values() { - let mut vm = Vm::new(); - let root = plain_object(&mut vm); - let getter = plain_object(&mut vm); - let setter = plain_object(&mut vm); - unsafe { - (*root).set_prop_at(0, JsValue::undefined()); - (*root).set_accessor_meta( - 0, - JsValue::from_js_object(getter), - JsValue::from_js_object(setter), - PropAttributes::DEFAULT_DATA, - ); - } - - let promoted = vm.promote_object(root); - let meta: PropMetaEntry = unsafe { &*promoted }.prop_meta_at(0).expect("meta"); - - assert!(!is_epoch_object(&vm, meta.get)); - assert!(!is_epoch_object(&vm, meta.set)); - } -} diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index d6973e3f..7bc1b48f 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -60,8 +60,8 @@ impl SessionGc { if ptr.is_null() { continue; } - // SAFETY: session_object_ptrs 中的指针只来自 promote_object_inner 的 - // session_epoch.alloc,在 arena 存活期间有效。 + // SAFETY: session_object_ptrs 中的指针来自统一入口的 Box 化分配 + // (alloc_object_box),对象堆内指针在释放前有效。 unsafe { (*ptr).set_gc_mark(false) }; } } @@ -1060,6 +1060,7 @@ impl Default for SessionGc { } } +#[allow(dead_code)] fn rewrite_forwarded_value( value: JsValue, forwarding: &HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>, ) -> JsValue { @@ -1103,6 +1104,7 @@ pub(crate) fn resolve_weak_key_sweep(key: weak_map::WeakKey) -> Option Option { Some(key) } @@ -1110,6 +1112,7 @@ pub(crate) fn resolve_weak_key_after_promotion(key: weak_map::WeakKey) -> Option /// 按 forwarding 表把全部 VM 根引用重写到搬移后的新地址。与 `for_each_value` /// 共用同一字段清单(经 `rewrite_values` 遍历)且须同步:遗漏字段会在搬移后 /// 保留指向旧 arena 的悬垂指针。 +#[allow(dead_code)] pub(crate) fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>) { vm_debug!("[GC] rewrite_vm_roots: {} forwarded objects", forwarding.len()); // 统一遍历:与 for_each_value 共用同一字段清单。 diff --git a/crates/oxide_vm/src/session_gc/tests.rs b/crates/oxide_vm/src/session_gc/tests.rs index c232333a..d68badb2 100644 --- a/crates/oxide_vm/src/session_gc/tests.rs +++ b/crates/oxide_vm/src/session_gc/tests.rs @@ -31,7 +31,7 @@ fn has_ptr(roots: &[JsValue], ptr: *mut JsObject) -> bool { fn uncaught_value_is_gc_root() { let mut vm = Vm::new(); let obj = plain_object(&mut vm); - let session = vm.promote_object(obj); + let session = obj; vm.last_uncaught_value = Some(JsValue::from_js_object(session)); let mut roots = Vec::new(); vm.for_each_root(|v| roots.push(v)); @@ -42,7 +42,7 @@ fn uncaught_value_is_gc_root() { fn pending_length_exception_is_gc_root() { let mut vm = Vm::new(); let obj = plain_object(&mut vm); - let session = vm.promote_object(obj); + let session = obj; vm.pending_length_exception = Some(JsValue::from_js_object(session)); let mut roots = Vec::new(); vm.for_each_root(|v| roots.push(v)); @@ -55,9 +55,9 @@ fn suspended_signal_fields_are_roots() { let a = plain_object(&mut vm); let b = plain_object(&mut vm); let c = plain_object(&mut vm); - let a_s = vm.promote_object(a); - let b_s = vm.promote_object(b); - let c_s = vm.promote_object(c); + let a_s = a; + let b_s = b; + let c_s = c; vm.generator_suspended = Some(JsValue::from_js_object(a_s)); vm.async_context = Some(JsValue::from_js_object(b_s)); vm.async_gen_context = Some(JsValue::from_js_object(c_s)); @@ -80,10 +80,10 @@ fn gc_roots_contains_registers_frames_and_root_roots() { (*root).set_prop_at(0, JsValue::from_js_object(child)); } - let root_session = vm.promote_object(root); - let frame_session = vm.promote_object(frame_obj); - let this_session = vm.promote_object(saved_this); - let child_session = vm.promote_object(child); + let root_session = root; + let frame_session = frame_obj; + let this_session = saved_this; + let child_session = child; vm.regs[0] = JsValue::from_js_object(root_session); vm.frames.push(CallFrame { @@ -138,9 +138,9 @@ fn rewrite_matches_roots_coverage() { // 测试同口径,共同构成孪生清单验证)。 let mut vm = Vm::new(); let obj = plain_object(&mut vm); - let session = vm.promote_object(obj); + let session = obj; let marker = plain_object(&mut vm); - let marker_session = vm.promote_object(marker); + let marker_session = marker; vm.regs[0] = JsValue::from_js_object(session); vm.last_uncaught_value = Some(JsValue::from_js_object(session)); vm.generator_suspended = Some(JsValue::from_js_object(session)); @@ -189,9 +189,9 @@ fn mark_phase_reaches_cycles_and_unreachable_are_unmarked() { (*reachable).set_prop_at(0, JsValue::from_js_object(root)); } - let root_session = vm.promote_object(root); + let root_session = root; let reachable_session = unsafe { (*root_session).get_prop_at(0).as_js_object_ptr() }; - let unreachable_session = vm.promote_object(unreachable); + let unreachable_session = unreachable; vm.regs[0] = JsValue::from_js_object(root_session); let mut gc = std::mem::take(&mut vm.gc_state.session_gc); @@ -215,9 +215,9 @@ fn sweep_preserves_cycle_and_collects_unreachable() { (*child).set_prop_at(0, JsValue::from_js_object(root)); } - let root_session = vm.promote_object(root); + let root_session = root; vm.regs[0] = JsValue::from_js_object(root_session); - let dead_session = vm.promote_object(dead); + let dead_session = dead; let mut gc = std::mem::take(&mut vm.gc_state.session_gc); gc.mark(&vm); let _ = gc.sweep(&mut vm); @@ -250,9 +250,9 @@ fn sweep_preserves_array_elements_and_collects_dead_element_object() { (*arr).set_prop_at(0, JsValue::from_js_object(live_elem)); (*arr).set_prop_at(1, JsValue::from_js_object(dead_elem)); } - // 晋升数组会把元素对象一并带入 session;随后断开元素 1 的引用,使其成为 + // 数组与其元素对象分配时同入 session 表;随后断开元素 1 的引用,使其成为 // mark 不可达的死对象,供 sweep 回收。 - let arr_session = vm.promote_object(arr); + let arr_session = arr; unsafe { (*arr_session).set_prop_at(1, JsValue::undefined()); } @@ -357,8 +357,7 @@ fn function_placeholder(vm: &mut Vm) -> JsValue { #[test] fn reset_maybe_collect_collects_after_threshold() { let mut vm = vm_with_low_threshold(); - let obj = plain_object(&mut vm); - vm.promote_object(obj); + plain_object(&mut vm); assert!(!vm.gc_state.session_object_ptrs.is_empty()); let tracked_before = vm.gc_state.session_object_ptrs.len(); @@ -374,8 +373,7 @@ fn reset_maybe_collect_collects_after_threshold() { #[test] fn gc_stats_summary_includes_collection() { let mut vm = vm_with_low_threshold(); - let obj = plain_object(&mut vm); - vm.promote_object(obj); + plain_object(&mut vm); vm.regs[0] = JsValue::undefined(); vm.maybe_collect_session_gc(); let summary = vm.gc_state.session_gc.stats_summary(); @@ -389,7 +387,7 @@ fn full_collect_keeps_global_root_edges_in_place() { unsafe { (*obj).set_prop_at(0, JsValue::int(42)); } - let old_ptr = vm.promote_object(obj); + let old_ptr = obj; let key = vm.kernel_core.perm_interner().intern("gcRoot").0; let global_ptr = vm.session.global_object().as_ptr() as *mut JsObject; unsafe { @@ -446,7 +444,7 @@ fn session_gc_traces_map_object_key_and_value() { vm.regs[2] = value; native_ok(map::map_set(&mut vm, &[0, 1, 2])); - let map_session = vm.promote_object(map_value.as_js_object_ptr()); + let map_session = map_value.as_js_object_ptr(); vm.regs.fill(JsValue::undefined()); vm.regs[0] = JsValue::from_js_object(map_session); let mut gc = std::mem::take(&mut vm.gc_state.session_gc); @@ -470,7 +468,7 @@ fn session_gc_traces_set_object_key() { vm.regs[1] = key; native_ok(set::set_add(&mut vm, &[0, 1])); - let set_session = vm.promote_object(set_value.as_js_object_ptr()); + let set_session = set_value.as_js_object_ptr(); vm.regs.fill(JsValue::undefined()); vm.regs[0] = JsValue::from_js_object(set_session); let mut gc = std::mem::take(&mut vm.gc_state.session_gc); @@ -510,11 +508,11 @@ fn weak_map_entry_survives_promotion_with_live_key() { vm.regs[0] = JsValue::from_js_object(wm); vm.regs[1] = JsValue::from_js_object(key); vm.regs[2] = JsValue::from_js_object(value); - let wm_session = vm.promote_object(wm); + let wm_session = wm; vm.regs.fill(JsValue::undefined()); vm.regs[0] = JsValue::from_js_object(wm_session); vm.regs[1] = JsValue::from_js_object(key); - // 值的强根取表内读回(统一入口后晋升为原样返回,读回即原件)。 + // 值的强根取表内读回(读回即原件)。 let value_root = oxide_builtins::weak_map::weak_map_probe_get(unsafe { &*wm_session }, JsValue::from_js_object(key)); vm.regs[2] = value_root; @@ -547,7 +545,7 @@ fn weak_map_entry_dropped_when_key_unrooted() { } vm.regs[0] = JsValue::from_js_object(wm); vm.regs[2] = JsValue::from_js_object(value); - let wm_session = vm.promote_object(wm); + let wm_session = wm; vm.regs.fill(JsValue::undefined()); vm.regs[0] = JsValue::from_js_object(wm_session); vm.regs[2] = JsValue::from_js_object(value); @@ -573,13 +571,13 @@ fn weak_map_value_edge_keeps_value_alive() { JsValue::from_js_object(value), ); } - // 值的独立根随后撤销,存活仅靠弱表值边(统一入口后晋升为原样返回)。 + // 值的独立根随后撤销,存活仅靠弱表值边。 vm.regs[2] = JsValue::from_js_object(value); - let value_session = vm.promote_object(value); + let value_session = value; vm.regs[2] = JsValue::from_js_object(value_session); vm.regs[0] = JsValue::from_js_object(wm); vm.regs[1] = JsValue::from_js_object(key); - let wm_session = vm.promote_object(wm); + let wm_session = wm; vm.regs.fill(JsValue::undefined()); vm.regs[0] = JsValue::from_js_object(wm_session); vm.regs[1] = JsValue::from_js_object(key); @@ -687,7 +685,7 @@ fn session_gc_traces_map_string_and_bigint_value() { vm.regs[2] = bi; native_ok(map::map_set(&mut vm, &[0, 1, 2])); - let map_session = vm.promote_object(map_value.as_js_object_ptr()); + let map_session = map_value.as_js_object_ptr(); vm.regs.fill(JsValue::undefined()); vm.regs[0] = JsValue::from_js_object(map_session); collect(&mut vm); @@ -714,7 +712,7 @@ fn session_gc_traces_set_string_and_bigint_value() { vm.regs[1] = bi; native_ok(set::set_add(&mut vm, &[0, 1])); - let set_session = vm.promote_object(set_value.as_js_object_ptr()); + let set_session = set_value.as_js_object_ptr(); vm.regs.fill(JsValue::undefined()); vm.regs[0] = JsValue::from_js_object(set_session); collect(&mut vm); @@ -744,7 +742,7 @@ fn session_gc_traces_dispose_stack_string_and_bigint_value() { vm.regs[1] = bi; native_ok(disposable_stack::disposable_stack_adopt(&mut vm, &[0, 1, 2])); - let stack_session = vm.promote_object(stack_value.as_js_object_ptr()); + let stack_session = stack_value.as_js_object_ptr(); vm.regs.fill(JsValue::undefined()); vm.regs[0] = JsValue::from_js_object(stack_session); collect(&mut vm); @@ -780,8 +778,8 @@ fn session_gc_traces_promise_string_and_bigint_result() { let bi_ptr = bi.as_bigint_ptr() as *mut num_bigint::BigInt; vm.fulfill_promise(promise2, bi).expect("fulfill bigint"); - let promise_session = vm.promote_object(promise.as_js_object_ptr()); - let promise2_session = vm.promote_object(promise2.as_js_object_ptr()); + let promise_session = promise.as_js_object_ptr(); + let promise2_session = promise2.as_js_object_ptr(); vm.regs.fill(JsValue::undefined()); vm.regs[0] = JsValue::from_js_object(promise_session); vm.regs[1] = JsValue::from_js_object(promise2_session); @@ -806,7 +804,7 @@ fn session_gc_traces_suspended_generator_bigint_value() { .expect("run"); let g_val = global_prop_opt(&vm, "g").expect("g 应挂在 global 上"); - let g_session = vm.promote_object(g_val.as_js_object_ptr()); + let g_session = g_val.as_js_object_ptr(); let g_obj = unsafe { &*g_session }; // SAFETY: 生成器状态盒经 Box::into_raw 挂对象构造,生命周期与对象一致。 let state = g_obj.native_data() as *mut crate::generator::GeneratorState; @@ -879,7 +877,7 @@ fn session_gc_traces_suspended_async_generator_bigint_value() { .expect("run"); let it_val = global_prop_opt(&vm, "it").expect("it 应挂在 global 上"); - let it_session = vm.promote_object(it_val.as_js_object_ptr()); + let it_session = it_val.as_js_object_ptr(); let it_obj = unsafe { &*it_session }; // SAFETY: 异步生成器状态盒经 Box::into_raw 挂迭代器构造,生命周期与对象一致。 let state = it_obj.native_data() as *mut crate::async_generator::AsyncGeneratorState; @@ -928,7 +926,7 @@ fn session_gc_keeps_shared_array_buffer_alive_through_view_native_edges() { (*root).set_prop_at(1, view); } - let root_session = vm.promote_object(root); + let root_session = root; vm.regs.fill(JsValue::undefined()); vm.regs[0] = JsValue::from_js_object(root_session); let mut gc = std::mem::take(&mut vm.gc_state.session_gc); @@ -981,7 +979,7 @@ fn session_gc_rewrites_buffer_retained_only_by_data_view_native_edge() { vm.regs[2] = JsValue::int(9); native_ok(data_view::data_view_set_int32(&mut vm, &[0, 1, 2])); - let view_session = vm.promote_object(view.as_js_object_ptr()); + let view_session = view.as_js_object_ptr(); vm.regs.fill(JsValue::undefined()); vm.regs[0] = JsValue::from_js_object(view_session); let mut gc = std::mem::take(&mut vm.gc_state.session_gc); @@ -1043,7 +1041,7 @@ fn session_string_survives_via_object_property() { unsafe { (*obj).set_prop_at(0, s); } - let obj_session = vm.promote_object(obj); + let obj_session = obj; // 该字符串仅通过存活对象的属性可达,不经过任何寄存器。 vm.regs.fill(JsValue::undefined()); @@ -1282,7 +1280,7 @@ fn suspended_state_box_cell_root_survives_collect() { .expect("run"); let g_val = global_prop_opt(&vm, "g").expect("g 应挂在 global 上"); - let g_session = vm.promote_object(g_val.as_js_object_ptr()); + let g_session = g_val.as_js_object_ptr(); let g_obj = unsafe { &*g_session }; // SAFETY: 生成器状态盒经 Box::into_raw 挂对象构造,生命周期与对象一致。 let state = g_obj.native_data() as *mut crate::generator::GeneratorState; @@ -1413,23 +1411,23 @@ fn strings_only_collection_preserves_all_root_kinds() { // 寄存器根:直接持有 session 串。 let reg_str = vm.new_string_owned("reg-root".repeat(16)); vm.regs[0] = reg_str; - // 存活对象属性根:session 对象经 promote 后持有 session 串(分配即触发回收, + // 存活对象属性根:session 对象持有 session 串(分配即触发回收, // reg_str 仍在寄存器,prop_str 挂到对象后才被下次回收看到)。 let obj = plain_object(&mut vm); let prop_str = vm.new_string_owned("prop-root".repeat(16)); unsafe { (*obj).set_prop_at(0, prop_str); } - let obj_session = vm.promote_object(obj); + let obj_session = obj; vm.regs[1] = JsValue::from_js_object(obj_session); - // 非 session 根对象属性:epoch 根对象(在寄存器)持有 session 串,mark 走 - // 非 session 根扫描路径保护。 - let epoch_obj = plain_object(&mut vm); + // 寄存器根对象属性:根对象(在寄存器)持有 session 串,mark 走根扫描 + // 路径保护。 + let root_obj = plain_object(&mut vm); let epoch_str = vm.new_string_owned("epoch-root".repeat(16)); unsafe { - (*epoch_obj).set_prop_at(0, epoch_str); + (*root_obj).set_prop_at(0, epoch_str); } - vm.regs[2] = JsValue::from_js_object(epoch_obj); + vm.regs[2] = JsValue::from_js_object(root_obj); // 死串:无任何根引用,最后手动触发一轮回收它。 let dead = vm.new_string_owned("dead".repeat(16)); let dead_ptr = dead.as_string_ptr_mut(); @@ -1450,7 +1448,7 @@ fn strings_only_collection_preserves_all_root_kinds() { fn strings_only_collection_does_not_move_objects() { let mut vm = vm_with_threshold(1); let obj = plain_object(&mut vm); - let obj_session = vm.promote_object(obj); + let obj_session = obj; vm.regs[0] = JsValue::from_js_object(obj_session); let before: Vec<_> = vm.gc_state.session_object_ptrs.clone(); @@ -1469,7 +1467,7 @@ fn strings_only_collection_does_not_move_objects() { fn multiple_strings_only_cycles_keep_objects_alive() { let mut vm = vm_with_threshold(1); let obj = plain_object(&mut vm); - let obj_session = vm.promote_object(obj); + let obj_session = obj; let live = vm.new_string_owned("keep".repeat(32)); unsafe { (*obj_session).set_prop_at(0, live); @@ -1500,7 +1498,7 @@ fn strings_only_then_full_collect_keeps_object_strings_live() { unsafe { (*obj).set_prop_at(0, s); } - let obj_session = vm.promote_object(obj); + let obj_session = obj; vm.regs[0] = JsValue::from_js_object(obj_session); // 第一轮 strings-only:对象被 mark 置位。收尾清残留,否则该位残留至完整 @@ -1523,7 +1521,7 @@ fn strings_only_then_full_collect_keeps_object_strings_live() { fn strings_only_collection_byte_accounting_matches_survivors() { let mut vm = vm_with_threshold(1); let obj = plain_object(&mut vm); - let obj_session = vm.promote_object(obj); + let obj_session = obj; vm.regs[0] = JsValue::from_js_object(obj_session); let live = vm.new_string_owned("live".repeat(8)); let live_ptr = live.as_string_ptr_mut(); @@ -1710,7 +1708,7 @@ fn full_reset_frees_rope_and_product() { // ── 执行期两档收集 ────────────────────────────────────────────────────── /// 执行期收集有效性:死 epoch 对象随换新 Bump 回收、死 session 对象原地 -/// 回收出表,活对象晋升 session 后值保持可读。 +/// 回收出表,活对象值保持可读。 #[test] fn in_run_collection_reclaims_dead_and_keeps_live() { let mut vm = vm_with_threshold(65536); @@ -1807,12 +1805,12 @@ fn in_run_collection_keeps_live_addresses_stable() { vm.run(&Arc::new(compile("globalThis.o = { a: [1, 2, 3], f: function() { return 1; } }; 0"))) .expect("run1"); - // 第一轮执行期收集:o 及其 epoch 子引用晋升 session。 + // 第一轮执行期收集:o 分配即入 session 表(统一入口)。 vm.maybe_collect_in_run(); assert!(vm.session_gc_stats().total_collections >= 1, "第一轮收集应执行"); let o = global_prop_opt(&vm, "o").expect("o 应挂在 global"); let o_ptr = o.as_js_object_ptr(); - assert!(unsafe { (*o_ptr).is_session_epoch() }, "o 晋升后应为 session 对象"); + assert!(unsafe { (*o_ptr).is_session_epoch() }, "o 应为 session 对象"); let a_ptr = vm .resolve_property(unsafe { &*o_ptr }, vm.kernel_core().perm_interner().intern("a").0) .expect("o.a") diff --git a/crates/oxide_vm/src/suspended.rs b/crates/oxide_vm/src/suspended.rs index 9aa6ccc0..f93bc3bd 100644 --- a/crates/oxide_vm/src/suspended.rs +++ b/crates/oxide_vm/src/suspended.rs @@ -243,6 +243,7 @@ impl SuspendedFrame { } /// GC 指针重写:与 `for_each_value` 字段一一对应。 + #[allow(dead_code)] pub fn rewrite_values(&mut self, mut rewrite: impl FnMut(JsValue) -> JsValue) { for v in self.regs.iter_mut() { *v = rewrite(*v); @@ -324,6 +325,7 @@ impl SuspendedFrame { } /// 深拷贝(promote / sweep 搬移用,替代 async/asyncgen 各自的 clone_*_with_rewrite)。 + #[allow(dead_code)] pub fn clone_with_rewrite(&self, mut rewrite: impl FnMut(JsValue) -> JsValue) -> Self { SuspendedFrame { regs: Box::new({ diff --git a/crates/oxide_vm/src/vm/gc_hooks.rs b/crates/oxide_vm/src/vm/gc_hooks.rs index d64a38b6..39064c06 100644 --- a/crates/oxide_vm/src/vm/gc_hooks.rs +++ b/crates/oxide_vm/src/vm/gc_hooks.rs @@ -107,6 +107,7 @@ impl Vm { } /// GC 指针重写(session 搬移后调用)。与 `for_each_value` 字段一一对应。 + #[allow(dead_code)] pub(crate) fn rewrite_values(&mut self, mut rewrite: impl FnMut(JsValue) -> JsValue) { for value in &mut self.regs { *value = rewrite(*value); diff --git a/crates/oxide_vm/src/vm/vmhost.rs b/crates/oxide_vm/src/vm/vmhost.rs index 49e1a8d7..99b96217 100644 --- a/crates/oxide_vm/src/vm/vmhost.rs +++ b/crates/oxide_vm/src/vm/vmhost.rs @@ -194,8 +194,8 @@ impl oxide_runtime_api::VmHost for Vm { || std::ptr::eq(p, self.async_function_proto.as_ptr()) || std::ptr::eq(p, self.async_generator_function_proto.as_ptr()) } - fn promote_if_needed_for_write_ptr(&mut self, target_ptr: *mut JsObject, value: JsValue) -> JsValue { - self.promote_if_needed_for_write_ptr(target_ptr, value) + fn promote_if_needed_for_write_ptr(&mut self, _target_ptr: *mut JsObject, value: JsValue) -> JsValue { + value } fn step_rng(&mut self) { self.step_rng() diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index fa92e243..7c0c20d3 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -624,8 +624,8 @@ impl Vm { /// # 注意事项 /// - 须在换表注册新代际**之前**调用(此刻 current_gen 仍指上一 run 的表, /// 新表未注册天然不被回收)。 - /// - 执行外安全点调用(dispatch 未重入、无在途 builtin 局部裸指针),与 - /// `promote_session_epoch_refs` 同前提:session 对象表此刻全部有效。 + /// - 执行外安全点调用(dispatch 未重入、无在途 builtin 局部裸指针), + /// session 对象表此刻全部有效。 pub(crate) fn reclaim_unreferenced_tables(&mut self) { // 存活代际集:函数对象只经创建点直落 session 对象表登记(或晋升克隆 // 随行),扫全表即完备;native 函数哨兵 sub_module_index == 0 不计数。 diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 8e6ea50e..54a2e250 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -549,9 +549,6 @@ impl Vm { pub fn reset(&mut self) { self.clear_execution_state(); self.maybe_collect_session_gc(); - // 晋升族空转调用保留:统一入口 Box 化后生产路径不产 epoch 对象, - // 本调用在统一表上恒无操作(随晋升族整体退役)。 - self.promote_session_epoch_refs(); self.bytecode = Arc::default(); // 表代际注册表不动:存活函数对象(含挂起帧 callee)按创建期代际仍须 // 命中原表,跨 run 调用与恢复靠它成立。active_immutables 指向的旧表 @@ -565,6 +562,11 @@ impl Vm { self.active_reg_limit = 0; } + /// 逃逸写屏障:写向全局/session 目标的对象值原样直通,不克隆。 + pub(crate) fn promote_if_needed_for_write_ptr(&mut self, _target_ptr: *mut JsObject, value: JsValue) -> JsValue { + value + } + /// 分配一个可被 session GC 回收的字符串 `JsValue`(session-heap 字符串)。 pub fn new_string(&mut self, s: &str) -> JsValue { self.new_string_owned(s.to_string()) diff --git a/crates/oxide_vm/tests/object_graph_retention_tests.rs b/crates/oxide_vm/tests/object_graph_retention_tests.rs index a98744fd..c9c091f5 100644 --- a/crates/oxide_vm/tests/object_graph_retention_tests.rs +++ b/crates/oxide_vm/tests/object_graph_retention_tests.rs @@ -1,13 +1,11 @@ -//! mem_object_graph 留存计数回归锚:逃逸写直通 + 测量点 promote 语义下的 -//! 确定性账目。 +//! mem_object_graph 留存计数回归锚:逃逸写直通下的确定性账目。 //! -//! 15 层满二叉树(32767 节点)在「写路径直通(不引克隆)+ 根 promote 深克隆」 -//! 晋升语义下的确定性计数。对象分配统一入口 Box 化后全部对象入统一表, -//! run 末统一表含整树节点字面量 + 每次 JS 调用无条件创建的 arguments 对象 + -//! 直 session 分配(函数对象与 prototype 子对象);promote_rooted 对 session -//! 对象全空转(统一表后无 epoch 原件可晋升)。晋升语义或根处理改动后,若以下 -//! 任一数值变化,说明 session 留存账目已变——须对照 tests/stress/mem_object_graph.js -//! 的基线(benchmark_baseline.json)复核后再更新本锚。 +//! 15 层满二叉树(32767 节点)的确定性计数。对象分配统一入口 Box 化后全部 +//! 对象入统一表,run 末统一表含整树节点字面量 + 每次 JS 调用无条件创建的 +//! arguments 对象 + 直 session 分配(函数对象与 prototype 子对象)。根处理 +//! 改动后,若以下任一数值变化,说明 session 留存账目已变——须对照 +//! tests/stress/mem_object_graph.js 的基线(benchmark_baseline.json)复核后 +//! 再更新本锚。 use std::sync::Arc; @@ -45,17 +43,14 @@ fn run_module(source: &str) -> Vm { /// 留存锚:对象分配统一入口 Box 化后 run 末统一表含全部对象——整树节点 /// 字面量 32767 + 每次 JS 调用无条件创建的 arguments 对象 32767 + 直 session -/// 分配 2(makeNode 函数对象与其 prototype 子对象)= 65536。promote_rooted -/// 对 session 对象全空转(统一表后无 epoch 原件可晋升);full GC 后逻辑存活集 -/// (整树 + 函数 2)在 session 内恰一份、无双计。 +/// 分配 2(makeNode 函数对象与其 prototype 子对象)= 65536;full GC 后逻辑 +/// 存活集(整树 + 函数 2)在 session 内恰一份、无双计。 #[test] fn object_graph_retention_anchor() { let mut vm = run_module(OBJECT_GRAPH); assert_eq!(vm.session_object_count(), 65536, "run 末统一表计数(节点 + arguments + 直分配)"); - vm.promote_rooted_epoch_objects(); - vm.promote_session_epoch_refs(); vm.collect_session_gc(); assert_eq!( vm.session_object_count(),