refactor: 晋升族整文件退役加生产调用点清理

This commit is contained in:
2026-10-02 16:55:04 +00:00
parent 3c6310c586
commit ab0b874281
17 changed files with 93 additions and 613 deletions
+1 -6
View File
@@ -108,12 +108,7 @@ pub fn run_js_stress_bench(config: &BenchConfig, kernel: &Arc<KernelCore>, pool:
let ic_hits = vm.ic_hit_count(); let ic_hits = vm.ic_hit_count();
let ic_misses = vm.ic_miss_count(); let ic_misses = vm.ic_miss_count();
// 留存口径:先让存活集对 session 完全可见——根直接持有的 epoch 对象 // 留存口径:强制完整 GC 清掉不可达垃圾后账目即留存堆。
// (顶层 var 等)晋升进 session,session 对象持有的 epoch 子引用
// (闭包捕获、原生盒直插)同样晋升(均与 epoch 边界同机制);
// 再强制完整 GC 清掉不可达垃圾,清扫后账目即 workload 留存堆
vm.promote_rooted_epoch_objects();
vm.promote_session_epoch_refs();
vm.collect_session_gc(); vm.collect_session_gc();
let retained_bytes = vm.session_bytes_allocated(); let retained_bytes = vm.session_bytes_allocated();
let retained_objects = vm.session_object_count(); let retained_objects = vm.session_object_count();
+2
View File
@@ -765,6 +765,7 @@ pub(crate) fn async_native_cell_edges(obj: &JsObject) -> Vec<*mut Cell> {
} }
/// 用转发函数重写状态快照中的所有 JsValue(session GC 移动式清扫 / promote 用)。 /// 用转发函数重写状态快照中的所有 JsValue(session GC 移动式清扫 / promote 用)。
#[allow(dead_code)]
pub(crate) fn rewrite_async_native(obj: &JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue) { pub(crate) fn rewrite_async_native(obj: &JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue) {
let Some(state) = async_state_mut(obj) else { let Some(state) = async_state_mut(obj) else {
return; return;
@@ -781,6 +782,7 @@ pub(crate) fn rewrite_async_native(obj: &JsObject, mut rewrite: impl FnMut(JsVal
} }
/// 深拷贝状态盒到新对象(promote / sweep 用):新对象持独立 Box,源盒可安全释放。 /// 深拷贝状态盒到新对象(promote / sweep 用):新对象持独立 Box,源盒可安全释放。
#[allow(dead_code)]
pub(crate) fn clone_async_native_with_rewrite( pub(crate) fn clone_async_native_with_rewrite(
old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue, old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue,
) { ) {
+2
View File
@@ -1753,6 +1753,7 @@ pub(crate) fn async_generator_native_cell_edges(obj: &JsObject) -> Vec<*mut Cell
} }
/// 用转发函数重写状态快照中的所有 JsValue(session GC 移动式清扫 / promote 用)。 /// 用转发函数重写状态快照中的所有 JsValue(session GC 移动式清扫 / promote 用)。
#[allow(dead_code)]
pub(crate) fn rewrite_async_generator_native(obj: &JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue) { pub(crate) fn rewrite_async_generator_native(obj: &JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue) {
let Some(state) = async_gen_state_mut(obj) else { let Some(state) = async_gen_state_mut(obj) else {
return; return;
@@ -1776,6 +1777,7 @@ pub(crate) fn rewrite_async_generator_native(obj: &JsObject, mut rewrite: impl F
} }
/// 深拷贝状态盒到新对象(promote / sweep 用):新对象持独立 Box,源盒可安全释放。 /// 深拷贝状态盒到新对象(promote / sweep 用):新对象持独立 Box,源盒可安全释放。
#[allow(dead_code)]
pub(crate) fn clone_async_generator_native_with_rewrite( pub(crate) fn clone_async_generator_native_with_rewrite(
old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue, old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue,
) { ) {
+2
View File
@@ -1023,6 +1023,7 @@ pub(crate) fn generator_native_cell_edges(obj: &JsObject) -> Vec<*mut Cell> {
} }
/// 用转发函数重写状态快照中的所有 JsValue(promote 用)。 /// 用转发函数重写状态快照中的所有 JsValue(promote 用)。
#[allow(dead_code)]
pub(crate) fn rewrite_generator_native(obj: &JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue) { pub(crate) fn rewrite_generator_native(obj: &JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue) {
let Some(state) = generator_state_mut(obj) else { let Some(state) = generator_state_mut(obj) else {
return; return;
@@ -1036,6 +1037,7 @@ pub(crate) fn rewrite_generator_native(obj: &JsObject, mut rewrite: impl FnMut(J
} }
/// 深拷贝状态盒到新对象(promote / sweep 用):新对象持独立 Box,源盒可安全释放。 /// 深拷贝状态盒到新对象(promote / sweep 用):新对象持独立 Box,源盒可安全释放。
#[allow(dead_code)]
pub(crate) fn clone_generator_native_with_rewrite( pub(crate) fn clone_generator_native_with_rewrite(
old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue, old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue,
) { ) {
-1
View File
@@ -23,7 +23,6 @@ pub mod native;
mod native_box_dispatch; mod native_box_dispatch;
/// Promise 运行时(状态盒 + 微任务队列 + 构造器/方法实现)。 /// Promise 运行时(状态盒 + 微任务队列 + 构造器/方法实现)。
pub mod promise; pub mod promise;
mod session_arena;
/// session 级 mark-sweep GC:标记-清扫 session arena 对象与 session 字符串。 /// session 级 mark-sweep GC:标记-清扫 session arena 对象与 session 字符串。
pub mod session_gc; pub mod session_gc;
mod suspended; mod suspended;
+4
View File
@@ -38,6 +38,7 @@ pub(crate) fn promise_native_edges(obj: &JsObject) -> Vec<JsValue> {
/// 结算链指针是裸指针边、不在本函数改写面:晋升克隆新分配于 session arena /// 结算链指针是裸指针边、不在本函数改写面:晋升克隆新分配于 session arena
/// (原件地址不变),改写场景指针天然有效;结算链接链由 /// (原件地址不变),改写场景指针天然有效;结算链接链由
/// `migrate_settlement_to_newest_clone` 负责。 /// `migrate_settlement_to_newest_clone` 负责。
#[allow(dead_code)]
pub(crate) fn rewrite_promise_native(obj: &JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue) { pub(crate) fn rewrite_promise_native(obj: &JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue) {
let Some(state) = promise_state_mut(obj) else { let Some(state) = promise_state_mut(obj) else {
return; return;
@@ -59,6 +60,7 @@ pub(crate) fn rewrite_promise_native(obj: &JsObject, mut rewrite: impl FnMut(JsV
/// 永久丢失、原件侧消费者永不触发;每条反应的 JsValue 随状态盒其余字段同一 /// 永久丢失、原件侧消费者永不触发;每条反应的 JsValue 随状态盒其余字段同一
/// pass 晋升/改写。结算链指针按原值保留(接链由 /// pass 晋升/改写。结算链指针按原值保留(接链由
/// `migrate_settlement_to_newest_clone` 负责)。 /// `migrate_settlement_to_newest_clone` 负责)。
#[allow(dead_code)]
pub(crate) fn clone_promise_native_with_rewrite( pub(crate) fn clone_promise_native_with_rewrite(
old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue, 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)不改结算 /// `clone_promise_native_with_rewrite`);session 搬移(GC sweep)不改结算
/// 拓扑,不得调用。 /// 拓扑,不得调用。
/// - 源非 pending 时无事可做直接返回(已结算原件不再传导)。 /// - 源非 pending 时无事可做直接返回(已结算原件不再传导)。
#[allow(dead_code)]
pub(crate) fn migrate_settlement_to_newest_clone( pub(crate) fn migrate_settlement_to_newest_clone(
old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue, 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 能力与处理器),源反应迁移 /// 改写单条反应的四条 JsValue 引用(派生 promise 能力与处理器),源反应迁移
/// 与再晋升合并共用。 /// 与再晋升合并共用。
#[allow(dead_code)]
fn rewrite_reaction<F: FnMut(JsValue) -> JsValue>(r: PromiseReaction, rewrite: &mut F) -> PromiseReaction { fn rewrite_reaction<F: FnMut(JsValue) -> JsValue>(r: PromiseReaction, rewrite: &mut F) -> PromiseReaction {
PromiseReaction { PromiseReaction {
promise: rewrite(r.promise), promise: rewrite(r.promise),
+1
View File
@@ -109,6 +109,7 @@ pub(crate) fn for_each_job_value(job: &Microtask, mut f: impl FnMut(JsValue)) {
} }
/// 供外部改写微任务队列中的 JsValue(session GC sweep 用)。 /// 供外部改写微任务队列中的 JsValue(session GC sweep 用)。
#[allow(dead_code)]
pub(crate) fn rewrite_job_values(job: &mut Microtask, mut rewrite: impl FnMut(JsValue) -> JsValue) { pub(crate) fn rewrite_job_values(job: &mut Microtask, mut rewrite: impl FnMut(JsValue) -> JsValue) {
match job { match job {
Microtask::Reaction { Microtask::Reaction {
+2 -4
View File
@@ -17,10 +17,7 @@ mod jobs;
mod reactions; mod reactions;
mod settlement; mod settlement;
pub(crate) use gc_edges::{ pub(crate) use gc_edges::{drop_promise_native, promise_native_edges, promise_native_size};
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 jobs::{for_each_job_value, rewrite_job_values}; pub(crate) use jobs::{for_each_job_value, rewrite_job_values};
pub use reactions::promise_settled_value; pub use reactions::promise_settled_value;
@@ -161,6 +158,7 @@ fn promise_state_ref(obj: &JsObject) -> Option<&PromiseState> {
} }
#[expect(clippy::mut_from_ref)] #[expect(clippy::mut_from_ref)]
#[allow(dead_code)]
fn promise_state_mut(obj: &JsObject) -> Option<&mut PromiseState> { fn promise_state_mut(obj: &JsObject) -> Option<&mut PromiseState> {
let ptr = obj.native_data() as *mut PromiseState; let ptr = obj.native_data() as *mut PromiseState;
if ptr.is_null() { if ptr.is_null() {
-524
View File
@@ -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::<JsObject>() + 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));
}
}
+5 -2
View File
@@ -60,8 +60,8 @@ impl SessionGc {
if ptr.is_null() { if ptr.is_null() {
continue; continue;
} }
// SAFETY: session_object_ptrs 中的指针只来自 promote_object_inner 的 // SAFETY: session_object_ptrs 中的指针来自统一入口的 Box 化分配
// session_epoch.alloc,在 arena 存活期间有效。 // (alloc_object_box),对象堆内指针在释放前有效。
unsafe { (*ptr).set_gc_mark(false) }; unsafe { (*ptr).set_gc_mark(false) };
} }
} }
@@ -1060,6 +1060,7 @@ impl Default for SessionGc {
} }
} }
#[allow(dead_code)]
fn rewrite_forwarded_value( fn rewrite_forwarded_value(
value: JsValue, forwarding: &HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>, value: JsValue, forwarding: &HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
) -> JsValue { ) -> JsValue {
@@ -1103,6 +1104,7 @@ pub(crate) fn resolve_weak_key_sweep(key: weak_map::WeakKey) -> Option<weak_map:
/// # 边界与前提 /// # 边界与前提
/// - 不解引用任何对象本体(无位域读取),无定夺时序面; /// - 不解引用任何对象本体(无位域读取),无定夺时序面;
/// - 晋升族整体为死代码保留面,弱表族本体退役时一并删除。 /// - 晋升族整体为死代码保留面,弱表族本体退役时一并删除。
#[allow(dead_code)]
pub(crate) fn resolve_weak_key_after_promotion(key: weak_map::WeakKey) -> Option<weak_map::WeakKey> { pub(crate) fn resolve_weak_key_after_promotion(key: weak_map::WeakKey) -> Option<weak_map::WeakKey> {
Some(key) Some(key)
} }
@@ -1110,6 +1112,7 @@ pub(crate) fn resolve_weak_key_after_promotion(key: weak_map::WeakKey) -> Option
/// 按 forwarding 表把全部 VM 根引用重写到搬移后的新地址。与 `for_each_value` /// 按 forwarding 表把全部 VM 根引用重写到搬移后的新地址。与 `for_each_value`
/// 共用同一字段清单(经 `rewrite_values` 遍历)且须同步:遗漏字段会在搬移后 /// 共用同一字段清单(经 `rewrite_values` 遍历)且须同步:遗漏字段会在搬移后
/// 保留指向旧 arena 的悬垂指针。 /// 保留指向旧 arena 的悬垂指针。
#[allow(dead_code)]
pub(crate) fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>) { 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()); vm_debug!("[GC] rewrite_vm_roots: {} forwarded objects", forwarding.len());
// 统一遍历:与 for_each_value 共用同一字段清单。 // 统一遍历:与 for_each_value 共用同一字段清单。
+53 -55
View File
@@ -31,7 +31,7 @@ fn has_ptr(roots: &[JsValue], ptr: *mut JsObject) -> bool {
fn uncaught_value_is_gc_root() { fn uncaught_value_is_gc_root() {
let mut vm = Vm::new(); let mut vm = Vm::new();
let obj = plain_object(&mut vm); 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)); vm.last_uncaught_value = Some(JsValue::from_js_object(session));
let mut roots = Vec::new(); let mut roots = Vec::new();
vm.for_each_root(|v| roots.push(v)); 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() { fn pending_length_exception_is_gc_root() {
let mut vm = Vm::new(); let mut vm = Vm::new();
let obj = plain_object(&mut vm); 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)); vm.pending_length_exception = Some(JsValue::from_js_object(session));
let mut roots = Vec::new(); let mut roots = Vec::new();
vm.for_each_root(|v| roots.push(v)); 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 a = plain_object(&mut vm);
let b = plain_object(&mut vm); let b = plain_object(&mut vm);
let c = plain_object(&mut vm); let c = plain_object(&mut vm);
let a_s = vm.promote_object(a); let a_s = a;
let b_s = vm.promote_object(b); let b_s = b;
let c_s = vm.promote_object(c); let c_s = c;
vm.generator_suspended = Some(JsValue::from_js_object(a_s)); vm.generator_suspended = Some(JsValue::from_js_object(a_s));
vm.async_context = Some(JsValue::from_js_object(b_s)); vm.async_context = Some(JsValue::from_js_object(b_s));
vm.async_gen_context = Some(JsValue::from_js_object(c_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)); (*root).set_prop_at(0, JsValue::from_js_object(child));
} }
let root_session = vm.promote_object(root); let root_session = root;
let frame_session = vm.promote_object(frame_obj); let frame_session = frame_obj;
let this_session = vm.promote_object(saved_this); let this_session = saved_this;
let child_session = vm.promote_object(child); let child_session = child;
vm.regs[0] = JsValue::from_js_object(root_session); vm.regs[0] = JsValue::from_js_object(root_session);
vm.frames.push(CallFrame { vm.frames.push(CallFrame {
@@ -138,9 +138,9 @@ fn rewrite_matches_roots_coverage() {
// 测试同口径,共同构成孪生清单验证)。 // 测试同口径,共同构成孪生清单验证)。
let mut vm = Vm::new(); let mut vm = Vm::new();
let obj = plain_object(&mut vm); let obj = plain_object(&mut vm);
let session = vm.promote_object(obj); let session = obj;
let marker = plain_object(&mut vm); 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.regs[0] = JsValue::from_js_object(session);
vm.last_uncaught_value = Some(JsValue::from_js_object(session)); vm.last_uncaught_value = Some(JsValue::from_js_object(session));
vm.generator_suspended = 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)); (*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 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); vm.regs[0] = JsValue::from_js_object(root_session);
let mut gc = std::mem::take(&mut vm.gc_state.session_gc); 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)); (*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); 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); let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
gc.mark(&vm); gc.mark(&vm);
let _ = gc.sweep(&mut 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(0, JsValue::from_js_object(live_elem));
(*arr).set_prop_at(1, JsValue::from_js_object(dead_elem)); (*arr).set_prop_at(1, JsValue::from_js_object(dead_elem));
} }
// 晋升数组会把元素对象一并带入 session;随后断开元素 1 的引用,使其成为 // 数组与其元素对象分配时同入 session 表;随后断开元素 1 的引用,使其成为
// mark 不可达的死对象,供 sweep 回收。 // mark 不可达的死对象,供 sweep 回收。
let arr_session = vm.promote_object(arr); let arr_session = arr;
unsafe { unsafe {
(*arr_session).set_prop_at(1, JsValue::undefined()); (*arr_session).set_prop_at(1, JsValue::undefined());
} }
@@ -357,8 +357,7 @@ fn function_placeholder(vm: &mut Vm) -> JsValue {
#[test] #[test]
fn reset_maybe_collect_collects_after_threshold() { fn reset_maybe_collect_collects_after_threshold() {
let mut vm = vm_with_low_threshold(); let mut vm = vm_with_low_threshold();
let obj = plain_object(&mut vm); plain_object(&mut vm);
vm.promote_object(obj);
assert!(!vm.gc_state.session_object_ptrs.is_empty()); assert!(!vm.gc_state.session_object_ptrs.is_empty());
let tracked_before = vm.gc_state.session_object_ptrs.len(); let tracked_before = vm.gc_state.session_object_ptrs.len();
@@ -374,8 +373,7 @@ fn reset_maybe_collect_collects_after_threshold() {
#[test] #[test]
fn gc_stats_summary_includes_collection() { fn gc_stats_summary_includes_collection() {
let mut vm = vm_with_low_threshold(); let mut vm = vm_with_low_threshold();
let obj = plain_object(&mut vm); plain_object(&mut vm);
vm.promote_object(obj);
vm.regs[0] = JsValue::undefined(); vm.regs[0] = JsValue::undefined();
vm.maybe_collect_session_gc(); vm.maybe_collect_session_gc();
let summary = vm.gc_state.session_gc.stats_summary(); let summary = vm.gc_state.session_gc.stats_summary();
@@ -389,7 +387,7 @@ fn full_collect_keeps_global_root_edges_in_place() {
unsafe { unsafe {
(*obj).set_prop_at(0, JsValue::int(42)); (*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 key = vm.kernel_core.perm_interner().intern("gcRoot").0;
let global_ptr = vm.session.global_object().as_ptr() as *mut JsObject; let global_ptr = vm.session.global_object().as_ptr() as *mut JsObject;
unsafe { unsafe {
@@ -446,7 +444,7 @@ fn session_gc_traces_map_object_key_and_value() {
vm.regs[2] = value; vm.regs[2] = value;
native_ok(map::map_set(&mut vm, &[0, 1, 2])); 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.fill(JsValue::undefined());
vm.regs[0] = JsValue::from_js_object(map_session); vm.regs[0] = JsValue::from_js_object(map_session);
let mut gc = std::mem::take(&mut vm.gc_state.session_gc); 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; vm.regs[1] = key;
native_ok(set::set_add(&mut vm, &[0, 1])); 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.fill(JsValue::undefined());
vm.regs[0] = JsValue::from_js_object(set_session); vm.regs[0] = JsValue::from_js_object(set_session);
let mut gc = std::mem::take(&mut vm.gc_state.session_gc); 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[0] = JsValue::from_js_object(wm);
vm.regs[1] = JsValue::from_js_object(key); vm.regs[1] = JsValue::from_js_object(key);
vm.regs[2] = JsValue::from_js_object(value); 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.fill(JsValue::undefined());
vm.regs[0] = JsValue::from_js_object(wm_session); vm.regs[0] = JsValue::from_js_object(wm_session);
vm.regs[1] = JsValue::from_js_object(key); vm.regs[1] = JsValue::from_js_object(key);
// 值的强根取表内读回(统一入口后晋升为原样返回,读回即原件)。 // 值的强根取表内读回(读回即原件)。
let value_root = let value_root =
oxide_builtins::weak_map::weak_map_probe_get(unsafe { &*wm_session }, JsValue::from_js_object(key)); oxide_builtins::weak_map::weak_map_probe_get(unsafe { &*wm_session }, JsValue::from_js_object(key));
vm.regs[2] = value_root; 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[0] = JsValue::from_js_object(wm);
vm.regs[2] = JsValue::from_js_object(value); 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.fill(JsValue::undefined());
vm.regs[0] = JsValue::from_js_object(wm_session); vm.regs[0] = JsValue::from_js_object(wm_session);
vm.regs[2] = JsValue::from_js_object(value); 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), JsValue::from_js_object(value),
); );
} }
// 值的独立根随后撤销,存活仅靠弱表值边(统一入口后晋升为原样返回)。 // 值的独立根随后撤销,存活仅靠弱表值边。
vm.regs[2] = 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[2] = JsValue::from_js_object(value_session);
vm.regs[0] = JsValue::from_js_object(wm); vm.regs[0] = JsValue::from_js_object(wm);
vm.regs[1] = JsValue::from_js_object(key); 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.fill(JsValue::undefined());
vm.regs[0] = JsValue::from_js_object(wm_session); vm.regs[0] = JsValue::from_js_object(wm_session);
vm.regs[1] = JsValue::from_js_object(key); 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; vm.regs[2] = bi;
native_ok(map::map_set(&mut vm, &[0, 1, 2])); 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.fill(JsValue::undefined());
vm.regs[0] = JsValue::from_js_object(map_session); vm.regs[0] = JsValue::from_js_object(map_session);
collect(&mut vm); collect(&mut vm);
@@ -714,7 +712,7 @@ fn session_gc_traces_set_string_and_bigint_value() {
vm.regs[1] = bi; vm.regs[1] = bi;
native_ok(set::set_add(&mut vm, &[0, 1])); 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.fill(JsValue::undefined());
vm.regs[0] = JsValue::from_js_object(set_session); vm.regs[0] = JsValue::from_js_object(set_session);
collect(&mut vm); collect(&mut vm);
@@ -744,7 +742,7 @@ fn session_gc_traces_dispose_stack_string_and_bigint_value() {
vm.regs[1] = bi; vm.regs[1] = bi;
native_ok(disposable_stack::disposable_stack_adopt(&mut vm, &[0, 1, 2])); 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.fill(JsValue::undefined());
vm.regs[0] = JsValue::from_js_object(stack_session); vm.regs[0] = JsValue::from_js_object(stack_session);
collect(&mut vm); 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; let bi_ptr = bi.as_bigint_ptr() as *mut num_bigint::BigInt;
vm.fulfill_promise(promise2, bi).expect("fulfill bigint"); vm.fulfill_promise(promise2, bi).expect("fulfill bigint");
let promise_session = vm.promote_object(promise.as_js_object_ptr()); let promise_session = promise.as_js_object_ptr();
let promise2_session = vm.promote_object(promise2.as_js_object_ptr()); let promise2_session = promise2.as_js_object_ptr();
vm.regs.fill(JsValue::undefined()); vm.regs.fill(JsValue::undefined());
vm.regs[0] = JsValue::from_js_object(promise_session); vm.regs[0] = JsValue::from_js_object(promise_session);
vm.regs[1] = JsValue::from_js_object(promise2_session); vm.regs[1] = JsValue::from_js_object(promise2_session);
@@ -806,7 +804,7 @@ fn session_gc_traces_suspended_generator_bigint_value() {
.expect("run"); .expect("run");
let g_val = global_prop_opt(&vm, "g").expect("g 应挂在 global 上"); 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 }; let g_obj = unsafe { &*g_session };
// SAFETY: 生成器状态盒经 Box::into_raw 挂对象构造,生命周期与对象一致。 // SAFETY: 生成器状态盒经 Box::into_raw 挂对象构造,生命周期与对象一致。
let state = g_obj.native_data() as *mut crate::generator::GeneratorState; 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"); .expect("run");
let it_val = global_prop_opt(&vm, "it").expect("it 应挂在 global 上"); 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 }; let it_obj = unsafe { &*it_session };
// SAFETY: 异步生成器状态盒经 Box::into_raw 挂迭代器构造,生命周期与对象一致。 // SAFETY: 异步生成器状态盒经 Box::into_raw 挂迭代器构造,生命周期与对象一致。
let state = it_obj.native_data() as *mut crate::async_generator::AsyncGeneratorState; 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); (*root).set_prop_at(1, view);
} }
let root_session = vm.promote_object(root); let root_session = root;
vm.regs.fill(JsValue::undefined()); vm.regs.fill(JsValue::undefined());
vm.regs[0] = JsValue::from_js_object(root_session); vm.regs[0] = JsValue::from_js_object(root_session);
let mut gc = std::mem::take(&mut vm.gc_state.session_gc); 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); vm.regs[2] = JsValue::int(9);
native_ok(data_view::data_view_set_int32(&mut vm, &[0, 1, 2])); 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.fill(JsValue::undefined());
vm.regs[0] = JsValue::from_js_object(view_session); vm.regs[0] = JsValue::from_js_object(view_session);
let mut gc = std::mem::take(&mut vm.gc_state.session_gc); let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
@@ -1043,7 +1041,7 @@ fn session_string_survives_via_object_property() {
unsafe { unsafe {
(*obj).set_prop_at(0, s); (*obj).set_prop_at(0, s);
} }
let obj_session = vm.promote_object(obj); let obj_session = obj;
// 该字符串仅通过存活对象的属性可达,不经过任何寄存器。 // 该字符串仅通过存活对象的属性可达,不经过任何寄存器。
vm.regs.fill(JsValue::undefined()); vm.regs.fill(JsValue::undefined());
@@ -1282,7 +1280,7 @@ fn suspended_state_box_cell_root_survives_collect() {
.expect("run"); .expect("run");
let g_val = global_prop_opt(&vm, "g").expect("g 应挂在 global 上"); 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 }; let g_obj = unsafe { &*g_session };
// SAFETY: 生成器状态盒经 Box::into_raw 挂对象构造,生命周期与对象一致。 // SAFETY: 生成器状态盒经 Box::into_raw 挂对象构造,生命周期与对象一致。
let state = g_obj.native_data() as *mut crate::generator::GeneratorState; let state = g_obj.native_data() as *mut crate::generator::GeneratorState;
@@ -1413,23 +1411,23 @@ fn strings_only_collection_preserves_all_root_kinds() {
// 寄存器根:直接持有 session 串。 // 寄存器根:直接持有 session 串。
let reg_str = vm.new_string_owned("reg-root".repeat(16)); let reg_str = vm.new_string_owned("reg-root".repeat(16));
vm.regs[0] = reg_str; vm.regs[0] = reg_str;
// 存活对象属性根:session 对象经 promote 后持有 session 串(分配即触发回收, // 存活对象属性根:session 对象持有 session 串(分配即触发回收,
// reg_str 仍在寄存器,prop_str 挂到对象后才被下次回收看到)。 // reg_str 仍在寄存器,prop_str 挂到对象后才被下次回收看到)。
let obj = plain_object(&mut vm); let obj = plain_object(&mut vm);
let prop_str = vm.new_string_owned("prop-root".repeat(16)); let prop_str = vm.new_string_owned("prop-root".repeat(16));
unsafe { unsafe {
(*obj).set_prop_at(0, prop_str); (*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); vm.regs[1] = JsValue::from_js_object(obj_session);
// 非 session 根对象属性:epoch 根对象(在寄存器)持有 session 串,mark 走 // 寄存器根对象属性:根对象(在寄存器)持有 session 串,mark 走根扫描
// 非 session 根扫描路径保护。 // 路径保护。
let epoch_obj = plain_object(&mut vm); let root_obj = plain_object(&mut vm);
let epoch_str = vm.new_string_owned("epoch-root".repeat(16)); let epoch_str = vm.new_string_owned("epoch-root".repeat(16));
unsafe { 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 = vm.new_string_owned("dead".repeat(16));
let dead_ptr = dead.as_string_ptr_mut(); 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() { fn strings_only_collection_does_not_move_objects() {
let mut vm = vm_with_threshold(1); let mut vm = vm_with_threshold(1);
let obj = plain_object(&mut vm); 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); vm.regs[0] = JsValue::from_js_object(obj_session);
let before: Vec<_> = vm.gc_state.session_object_ptrs.clone(); 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() { fn multiple_strings_only_cycles_keep_objects_alive() {
let mut vm = vm_with_threshold(1); let mut vm = vm_with_threshold(1);
let obj = plain_object(&mut vm); 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)); let live = vm.new_string_owned("keep".repeat(32));
unsafe { unsafe {
(*obj_session).set_prop_at(0, live); (*obj_session).set_prop_at(0, live);
@@ -1500,7 +1498,7 @@ fn strings_only_then_full_collect_keeps_object_strings_live() {
unsafe { unsafe {
(*obj).set_prop_at(0, s); (*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); vm.regs[0] = JsValue::from_js_object(obj_session);
// 第一轮 strings-only:对象被 mark 置位。收尾清残留,否则该位残留至完整 // 第一轮 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() { fn strings_only_collection_byte_accounting_matches_survivors() {
let mut vm = vm_with_threshold(1); let mut vm = vm_with_threshold(1);
let obj = plain_object(&mut vm); 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); vm.regs[0] = JsValue::from_js_object(obj_session);
let live = vm.new_string_owned("live".repeat(8)); let live = vm.new_string_owned("live".repeat(8));
let live_ptr = live.as_string_ptr_mut(); let live_ptr = live.as_string_ptr_mut();
@@ -1710,7 +1708,7 @@ fn full_reset_frees_rope_and_product() {
// ── 执行期两档收集 ────────────────────────────────────────────────────── // ── 执行期两档收集 ──────────────────────────────────────────────────────
/// 执行期收集有效性:死 epoch 对象随换新 Bump 回收、死 session 对象原地 /// 执行期收集有效性:死 epoch 对象随换新 Bump 回收、死 session 对象原地
/// 回收出表,活对象晋升 session 后值保持可读。 /// 回收出表,活对象值保持可读。
#[test] #[test]
fn in_run_collection_reclaims_dead_and_keeps_live() { fn in_run_collection_reclaims_dead_and_keeps_live() {
let mut vm = vm_with_threshold(65536); 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"))) vm.run(&Arc::new(compile("globalThis.o = { a: [1, 2, 3], f: function() { return 1; } }; 0")))
.expect("run1"); .expect("run1");
// 第一轮执行期收集:o 及其 epoch 子引用晋升 session。 // 第一轮执行期收集:o 分配即入 session 表(统一入口)。
vm.maybe_collect_in_run(); vm.maybe_collect_in_run();
assert!(vm.session_gc_stats().total_collections >= 1, "第一轮收集应执行"); assert!(vm.session_gc_stats().total_collections >= 1, "第一轮收集应执行");
let o = global_prop_opt(&vm, "o").expect("o 应挂在 global"); let o = global_prop_opt(&vm, "o").expect("o 应挂在 global");
let o_ptr = o.as_js_object_ptr(); 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 let a_ptr = vm
.resolve_property(unsafe { &*o_ptr }, vm.kernel_core().perm_interner().intern("a").0) .resolve_property(unsafe { &*o_ptr }, vm.kernel_core().perm_interner().intern("a").0)
.expect("o.a") .expect("o.a")
+2
View File
@@ -243,6 +243,7 @@ impl SuspendedFrame {
} }
/// GC 指针重写:与 `for_each_value` 字段一一对应。 /// GC 指针重写:与 `for_each_value` 字段一一对应。
#[allow(dead_code)]
pub fn rewrite_values(&mut self, mut rewrite: impl FnMut(JsValue) -> JsValue) { pub fn rewrite_values(&mut self, mut rewrite: impl FnMut(JsValue) -> JsValue) {
for v in self.regs.iter_mut() { for v in self.regs.iter_mut() {
*v = rewrite(*v); *v = rewrite(*v);
@@ -324,6 +325,7 @@ impl SuspendedFrame {
} }
/// 深拷贝(promote / sweep 搬移用,替代 async/asyncgen 各自的 clone_*_with_rewrite)。 /// 深拷贝(promote / sweep 搬移用,替代 async/asyncgen 各自的 clone_*_with_rewrite)。
#[allow(dead_code)]
pub fn clone_with_rewrite(&self, mut rewrite: impl FnMut(JsValue) -> JsValue) -> Self { pub fn clone_with_rewrite(&self, mut rewrite: impl FnMut(JsValue) -> JsValue) -> Self {
SuspendedFrame { SuspendedFrame {
regs: Box::new({ regs: Box::new({
+1
View File
@@ -107,6 +107,7 @@ impl Vm {
} }
/// GC 指针重写(session 搬移后调用)。与 `for_each_value` 字段一一对应。 /// GC 指针重写(session 搬移后调用)。与 `for_each_value` 字段一一对应。
#[allow(dead_code)]
pub(crate) fn rewrite_values(&mut self, mut rewrite: impl FnMut(JsValue) -> JsValue) { pub(crate) fn rewrite_values(&mut self, mut rewrite: impl FnMut(JsValue) -> JsValue) {
for value in &mut self.regs { for value in &mut self.regs {
*value = rewrite(*value); *value = rewrite(*value);
+2 -2
View File
@@ -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_function_proto.as_ptr())
|| std::ptr::eq(p, self.async_generator_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 { 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) value
} }
fn step_rng(&mut self) { fn step_rng(&mut self) {
self.step_rng() self.step_rng()
+2 -2
View File
@@ -624,8 +624,8 @@ impl Vm {
/// # 注意事项 /// # 注意事项
/// - 须在换表注册新代际**之前**调用(此刻 current_gen 仍指上一 run 的表, /// - 须在换表注册新代际**之前**调用(此刻 current_gen 仍指上一 run 的表,
/// 新表未注册天然不被回收)。 /// 新表未注册天然不被回收)。
/// - 执行外安全点调用(dispatch 未重入、无在途 builtin 局部裸指针),与 /// - 执行外安全点调用(dispatch 未重入、无在途 builtin 局部裸指针),
/// `promote_session_epoch_refs` 同前提:session 对象表此刻全部有效。 /// session 对象表此刻全部有效。
pub(crate) fn reclaim_unreferenced_tables(&mut self) { pub(crate) fn reclaim_unreferenced_tables(&mut self) {
// 存活代际集:函数对象只经创建点直落 session 对象表登记(或晋升克隆 // 存活代际集:函数对象只经创建点直落 session 对象表登记(或晋升克隆
// 随行),扫全表即完备;native 函数哨兵 sub_module_index == 0 不计数。 // 随行),扫全表即完备;native 函数哨兵 sub_module_index == 0 不计数。
+5 -3
View File
@@ -549,9 +549,6 @@ impl Vm {
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.clear_execution_state(); self.clear_execution_state();
self.maybe_collect_session_gc(); self.maybe_collect_session_gc();
// 晋升族空转调用保留:统一入口 Box 化后生产路径不产 epoch 对象,
// 本调用在统一表上恒无操作(随晋升族整体退役)。
self.promote_session_epoch_refs();
self.bytecode = Arc::default(); self.bytecode = Arc::default();
// 表代际注册表不动:存活函数对象(含挂起帧 callee)按创建期代际仍须 // 表代际注册表不动:存活函数对象(含挂起帧 callee)按创建期代际仍须
// 命中原表,跨 run 调用与恢复靠它成立。active_immutables 指向的旧表 // 命中原表,跨 run 调用与恢复靠它成立。active_immutables 指向的旧表
@@ -565,6 +562,11 @@ impl Vm {
self.active_reg_limit = 0; 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 字符串)。 /// 分配一个可被 session GC 回收的字符串 `JsValue`(session-heap 字符串)。
pub fn new_string(&mut self, s: &str) -> JsValue { pub fn new_string(&mut self, s: &str) -> JsValue {
self.new_string_owned(s.to_string()) self.new_string_owned(s.to_string())
@@ -1,13 +1,11 @@
//! mem_object_graph 留存计数回归锚:逃逸写直通 + 测量点 promote 语义下的 //! mem_object_graph 留存计数回归锚:逃逸写直通下的确定性账目。
//! 确定性账目。
//! //!
//! 15 层满二叉树(32767 节点)在「写路径直通(不引克隆)+ 根 promote 深克隆」 //! 15 层满二叉树(32767 节点)的确定性计数。对象分配统一入口 Box 化后全部
//! 晋升语义下的确定性计数。对象分配统一入口 Box 化后全部对象入统一表, //! 对象入统一表,run 末统一表含整树节点字面量 + 每次 JS 调用无条件创建的
//! run 末统一表含整树节点字面量 + 每次 JS 调用无条件创建的 arguments 对象 + //! arguments 对象 + 直 session 分配(函数对象与 prototype 子对象)。根处理
//! 直 session 分配(函数对象与 prototype 子对象);promote_rooted 对 session //! 改动后,若以下任一数值变化,说明 session 留存账目已变——须对照
//! 对象全空转(统一表后无 epoch 原件可晋升)。晋升语义或根处理改动后,若以下 //! tests/stress/mem_object_graph.js 的基线(benchmark_baseline.json)复核后
//! 任一数值变化,说明 session 留存账目已变——须对照 tests/stress/mem_object_graph.js //! 再更新本锚。
//! 的基线(benchmark_baseline.json)复核后再更新本锚。
use std::sync::Arc; use std::sync::Arc;
@@ -45,17 +43,14 @@ fn run_module(source: &str) -> Vm {
/// 留存锚:对象分配统一入口 Box 化后 run 末统一表含全部对象——整树节点 /// 留存锚:对象分配统一入口 Box 化后 run 末统一表含全部对象——整树节点
/// 字面量 32767 + 每次 JS 调用无条件创建的 arguments 对象 32767 + 直 session /// 字面量 32767 + 每次 JS 调用无条件创建的 arguments 对象 32767 + 直 session
/// 分配 2(makeNode 函数对象与其 prototype 子对象)= 65536。promote_rooted /// 分配 2(makeNode 函数对象与其 prototype 子对象)= 65536;full GC 后逻辑
/// 对 session 对象全空转(统一表后无 epoch 原件可晋升);full GC 后逻辑存活集 /// 存活集(整树 + 函数 2)在 session 内恰一份、无双计。
/// (整树 + 函数 2)在 session 内恰一份、无双计。
#[test] #[test]
fn object_graph_retention_anchor() { fn object_graph_retention_anchor() {
let mut vm = run_module(OBJECT_GRAPH); let mut vm = run_module(OBJECT_GRAPH);
assert_eq!(vm.session_object_count(), 65536, "run 末统一表计数(节点 + arguments + 直分配)"); assert_eq!(vm.session_object_count(), 65536, "run 末统一表计数(节点 + arguments + 直分配)");
vm.promote_rooted_epoch_objects();
vm.promote_session_epoch_refs();
vm.collect_session_gc(); vm.collect_session_gc();
assert_eq!( assert_eq!(
vm.session_object_count(), vm.session_object_count(),