mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
perf: 执行期 GC 收集——扩展 mark 跟随 epoch 臂、活 epoch 对象晋升 session(克隆体置活防同轮原地 sweep 误放)、死 session 对象原地释放出表,for-in 门控(活跃迭代器 + 挂起状态盒)与分配包络水位触发
This commit is contained in:
@@ -478,11 +478,20 @@ fn async_state_mut(obj: &JsObject) -> Option<&mut AsyncState> {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: native_data 在 create_async_context 中由 Box::into_raw 分配,
|
||||
// 生命周期与异步上下文对象一致;GC 只在 reset(无执行状态)时运行。
|
||||
// 生命周期与异步上下文对象一致;GC 路径(mark 边收集、执行期门控扫描)只读状态盒。
|
||||
Some(unsafe { &mut *ptr })
|
||||
}
|
||||
}
|
||||
|
||||
/// 异步挂起帧是否持有 for-in 迭代器:执行期收集门控按指针扫描
|
||||
/// (ForInIter body 分配于 epoch arena,换新 Bump 即时失效在表迭代器)。
|
||||
pub(crate) fn async_holds_suspended_for_in(obj: &JsObject) -> bool {
|
||||
let Some(state) = async_state_mut(obj) else {
|
||||
return false;
|
||||
};
|
||||
!state.suspended.for_in_iters.is_empty()
|
||||
}
|
||||
|
||||
/// 异步状态内所有对象引用的扁平列表(GC mark 边)。
|
||||
pub(crate) fn async_native_edges(obj: &JsObject) -> Vec<JsValue> {
|
||||
let Some(state) = async_state_mut(obj) else {
|
||||
|
||||
@@ -864,11 +864,20 @@ fn async_gen_state_mut(obj: &JsObject) -> Option<&mut AsyncGeneratorState> {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: native_data 在 create_async_generator_object 中由 Box::into_raw 分配,
|
||||
// 生命周期与异步生成器对象一致;GC 只在 reset(无执行状态)时运行。
|
||||
// 生命周期与异步生成器对象一致;GC 路径(mark 边收集、执行期门控扫描)只读状态盒。
|
||||
Some(unsafe { &mut *ptr })
|
||||
}
|
||||
}
|
||||
|
||||
/// 异步生成器挂起帧是否持有 for-in 迭代器:执行期收集门控按指针扫描
|
||||
/// (ForInIter body 分配于 epoch arena,换新 Bump 即时失效在表迭代器)。
|
||||
pub(crate) fn async_generator_holds_suspended_for_in(obj: &JsObject) -> bool {
|
||||
let Some(state) = async_gen_state_mut(obj) else {
|
||||
return false;
|
||||
};
|
||||
!state.suspended.for_in_iters.is_empty()
|
||||
}
|
||||
|
||||
/// 异步生成器状态内所有对象引用的扁平列表(GC mark 边)。
|
||||
pub(crate) fn async_generator_native_edges(obj: &JsObject) -> Vec<JsValue> {
|
||||
let Some(state) = async_gen_state_mut(obj) else {
|
||||
|
||||
@@ -859,11 +859,20 @@ fn generator_state_mut(obj: &JsObject) -> Option<&mut GeneratorState> {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: native_data 在 create_generator_object 中由 Box::into_raw 分配,
|
||||
// 生命周期与生成器对象一致;GC 只在 reset(无执行状态)时运行。
|
||||
// 生命周期与生成器对象一致;GC 路径(mark 边收集、执行期门控扫描)只读状态盒。
|
||||
Some(unsafe { &mut *ptr })
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成器挂起帧是否持有 for-in 迭代器:执行期收集门控按指针扫描
|
||||
/// (ForInIter body 分配于 epoch arena,换新 Bump 即时失效在表迭代器)。
|
||||
pub(crate) fn generator_holds_suspended_for_in(obj: &JsObject) -> bool {
|
||||
let Some(state) = generator_state_mut(obj) else {
|
||||
return false;
|
||||
};
|
||||
!state.suspended.for_in_iters.is_empty()
|
||||
}
|
||||
|
||||
/// 生成器状态内所有对象引用的扁平列表(GC mark 边)。
|
||||
pub(crate) fn generator_native_edges(obj: &JsObject) -> Vec<JsValue> {
|
||||
let Some(state) = generator_state_mut(obj) else {
|
||||
|
||||
@@ -141,53 +141,69 @@ impl Vm {
|
||||
return;
|
||||
}
|
||||
let mut forwarding = std::mem::take(&mut self.gc_state.forwarding);
|
||||
for &ptr in &objects {
|
||||
self.rewrite_session_epoch_refs(&objects, &mut forwarding);
|
||||
forwarding.clear();
|
||||
self.gc_state.forwarding = forwarding;
|
||||
// 改写期新克隆已随晋升推入 gc_state 侧的表(此刻仅含克隆体),把取出的
|
||||
// 旧对象按原相对序拼回同一表,表 = 克隆体 + 旧对象,各恰登记一次。
|
||||
self.gc_state.session_object_ptrs.extend(objects);
|
||||
}
|
||||
|
||||
/// 按调用方给定的转发表把对象列表的 epoch 子引用就地改写:未在上游晋升过的
|
||||
/// epoch 子对象经转发表克隆进 session(递归去重共享与环),已是 session/
|
||||
/// 非 epoch 的值原样保留。JS 边与 native 状态盒走同一改写闭包。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// - 调用方持有转发表期间不得让其它晋升路径改动 `gc_state.forwarding`;
|
||||
/// 晋升新克隆推入的对象表由调用方决定何时拼回。
|
||||
/// - 死对象(不可达)的 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, &mut forwarding));
|
||||
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, &mut forwarding));
|
||||
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, &mut forwarding));
|
||||
set::rewrite_set_native(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, &mut forwarding)
|
||||
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, &mut forwarding)
|
||||
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, &mut forwarding)
|
||||
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, &mut forwarding)
|
||||
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, &mut forwarding)
|
||||
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, &mut forwarding)
|
||||
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, &mut forwarding)
|
||||
self.promote_value_if_epoch_object(value, forwarding)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
forwarding.clear();
|
||||
self.gc_state.forwarding = forwarding;
|
||||
// 晋升过程新克隆的对象已推入 gc_state 侧的表,拼回旧表保持原序在前。
|
||||
self.gc_state.session_object_ptrs.extend(objects);
|
||||
}
|
||||
|
||||
/// 把根直接持有的 epoch 对象(顶层 var 寄存器、挂起句柄等)晋升进 session,
|
||||
|
||||
@@ -52,6 +52,16 @@ impl SessionGc {
|
||||
// session_epoch.alloc,在 arena 存活期间有效。
|
||||
unsafe { (*ptr).set_gc_mark(false) };
|
||||
}
|
||||
// epoch 臂同清:mark 的 epoch 臂置位不随 session 清位消除,残留位会让
|
||||
// 下一次 mark 的 DFS 短路、漏扫该对象此间新增的边。
|
||||
for &ptr in &vm.gc_state.epoch_object_ptrs {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: epoch_object_ptrs 中的指针只来自 alloc_object 的 epoch.alloc,
|
||||
// 在 epoch 存活期间有效。
|
||||
unsafe { (*ptr).set_gc_mark(false) };
|
||||
}
|
||||
}
|
||||
|
||||
/// 只读核算 session 对象的堆数据字节(属性/元素/meta Vec capacity + upvalue 列表
|
||||
@@ -220,8 +230,15 @@ impl SessionGc {
|
||||
) {
|
||||
if value.is_object() {
|
||||
let ptr = value.as_js_object_ptr();
|
||||
// session 边与 epoch 边同栈:epoch 对象的 GC_MARK 位与 session 同字节
|
||||
// 可置可读,晋升收集按"标记 ∩ epoch 表"取活集。
|
||||
if vm.is_session_ptr(ptr) {
|
||||
stack.push(ptr);
|
||||
} else if !ptr.is_null() {
|
||||
// SAFETY: 执行核心产出的对象值,指针在 session 生命周期内有效。
|
||||
if unsafe { (&*ptr).is_epoch() } {
|
||||
stack.push(ptr);
|
||||
}
|
||||
}
|
||||
} else if value.is_string() {
|
||||
Self::mark_string_live(live_strings, value.as_string_ptr_mut());
|
||||
@@ -397,9 +414,16 @@ impl SessionGc {
|
||||
stack.push(ptr);
|
||||
continue;
|
||||
}
|
||||
// SAFETY: 对象根由 VM 自有的字段与 builtin 对象产生。
|
||||
// SAFETY: 对象根由 VM 自有的字段与 builtin 对象产生,指针合法。
|
||||
unsafe {
|
||||
let obj = &*ptr;
|
||||
// epoch 根(顶层 var 寄存器等)自身入栈置位:晋升收集按标记位
|
||||
// 判活,根不置位会被误判为死。
|
||||
if obj.is_epoch() {
|
||||
stack.push(ptr);
|
||||
continue;
|
||||
}
|
||||
// P 对象根(builtin world / global)不回收,只扫边。
|
||||
Self::scan_edges_for_mark(obj, vm, stack, live_strings, live_bigints);
|
||||
}
|
||||
}
|
||||
@@ -727,18 +751,6 @@ impl SessionGc {
|
||||
&& vm.gc_state.session_bytes_allocated >= vm.kernel_core().config().session_gc_threshold
|
||||
}
|
||||
|
||||
/// 执行期完整 GC 的触发判断:账目须超过水位(上次收集后的存活字节 +
|
||||
/// 阈值增量)。与 `should_collect`(reset 路径用)不同,本方法走增量水位,
|
||||
/// 防止存活对象超阈值时每指令重复触发无死对象可回收的白跑。
|
||||
/// 预留给 17.3b(对象侧执行期触发)安全点审计后使用。
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn should_collect_gc(&self, vm: &Vm) -> bool {
|
||||
(!vm.gc_state.session_object_ptrs.is_empty()
|
||||
|| !vm.gc_state.session_string_ptrs.is_empty()
|
||||
|| !vm.gc_state.session_bigint_ptrs.borrow().is_empty())
|
||||
&& vm.gc_state.session_bytes_allocated >= vm.gc_state.gc_watermark
|
||||
}
|
||||
|
||||
/// 执行期字符串回收的触发判断:账目须超过水位(上次收集后的存活字节 +
|
||||
/// 阈值增量)。完整收集(reset)仍用 [`Self::should_collect`] 的阈值直接比较,
|
||||
/// 保证死对象超阈值即被 reset 回收;执行期走增量水位,活串超阈值时不每指令
|
||||
@@ -886,16 +898,216 @@ impl SessionGc {
|
||||
}
|
||||
}
|
||||
|
||||
/// 执行期完整 GC 入口:按水位判定,触发后抬高水位。
|
||||
/// 预留给 17.3b(对象侧执行期触发)安全点审计后使用。
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn maybe_collect_gc(&mut self, vm: &mut Vm) {
|
||||
if self.should_collect_gc(vm) {
|
||||
self.collect(vm);
|
||||
// 抬高下次触发水位:存活字节 + 阈值增量,避免活对象超阈值时每指令重复触发。
|
||||
let threshold = vm.gc_state.gc_threshold_cached;
|
||||
vm.gc_state.gc_watermark = vm.gc_state.session_bytes_allocated.saturating_add(threshold);
|
||||
/// 执行期两档收集:epoch 晋升档 + session 原地非移动 sweep 档。
|
||||
///
|
||||
/// 在 dispatch 安全点(循环顶 + `native_call_depth == 0`)回收单 run 分配
|
||||
/// 包络内的死对象:
|
||||
/// - epoch 晋升档:扩展 mark(epoch 臂跟随)定活集,活 epoch 对象晋升
|
||||
/// session(递归克隆 + forwarding 去重共享与环 + native 盒深拷),根与
|
||||
/// session 对象的 epoch 子引用按转发表改写,全部 epoch 旧堆区与独占
|
||||
/// upvalue 列表释放,epoch 换新 Bump;
|
||||
/// - session 原地 sweep 档:死 session 对象原地释放(堆区 + upvalue 列表)
|
||||
/// 并出表;存活对象不移动——地址不变、免 forwarding/rewrite,用户可观察
|
||||
/// identity 不分裂。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 调用方已完成门控(无活跃/挂起 for-in):ForInIter body 分配于 epoch
|
||||
/// arena,换新 Bump 即时失效在表迭代器;
|
||||
/// - 调用点为 dispatch 安全点(`native_call_depth == 0`),无在途 builtin
|
||||
/// 局部裸指针、dispatch 未重入。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - `epoch_object_ptrs` 清空、epoch Bump 换新(旧 arena 全量归还);
|
||||
/// - `session_object_ptrs` 仅剩存活,`session_bytes_allocated` 按存活重算
|
||||
/// (串/BigInt 归 strings-only 路径,本路径不动,仅对象口径变化);
|
||||
/// - `gc_watermark` = 当前分配包络 + 阈值增量;
|
||||
/// - 累计/时长统计更新;mark 位全清(收集后无残留)。
|
||||
pub(crate) fn collect_in_run(&mut self, vm: &mut Vm) {
|
||||
let start = Instant::now();
|
||||
vm_info!("[GC] in-run cycle #{} start", self.total_collections + 1);
|
||||
|
||||
// 清残留 mark 位(session + epoch):防 mark DFS 因历史 true 短路漏标。
|
||||
self.clear_all_marks(vm);
|
||||
|
||||
// 扩展 mark:epoch 臂跟随,活 epoch 集 = 被标记 ∩ epoch 表。
|
||||
self.mark(vm);
|
||||
|
||||
// ── epoch 晋升档 ──
|
||||
|
||||
let mut forwarding = std::mem::take(&mut vm.gc_state.forwarding);
|
||||
let epoch_ptrs = std::mem::take(&mut vm.gc_state.epoch_object_ptrs);
|
||||
|
||||
// 活 epoch 对象逐个晋升:递归克隆携带 native 盒,共享/环经 forwarding
|
||||
// 去重;死对象留在 epoch arena,换新 Bump 前统一释放。
|
||||
let mut live_epoch = 0u64;
|
||||
for &ptr in &epoch_ptrs {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: ptr 来自 epoch 对象表登记,arena 存活期内有效。
|
||||
if unsafe { (*ptr).is_gc_marked() } {
|
||||
vm.promote_object_inner(ptr, &mut forwarding);
|
||||
live_epoch += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 晋升克隆体自身即活:克隆不携带源对象标记位,而紧随其后的 session
|
||||
// 原地 sweep 按标记位保活——不置位则克隆体在同一轮内被当作死对象
|
||||
// 原地释放,而根已改写指向克隆体(悬垂)。此点转发表值集恰为本轮
|
||||
// 晋升克隆体全集(session 原地改写路径的补晋升发生在其后、不入
|
||||
// 本轮 sweep 集,无需置位)。
|
||||
for &dst in forwarding.values() {
|
||||
// SAFETY: dst 为本轮晋升克隆体,session arena 存活期内有效。
|
||||
unsafe { (*dst).set_gc_mark(true) };
|
||||
}
|
||||
|
||||
// 根持有的 epoch 对象(顶层 var 寄存器等)按转发表解析到克隆体。
|
||||
rewrite_vm_roots(vm, &forwarding);
|
||||
|
||||
// session 对象可持有绕过写屏障的 epoch 子引用(函数 captured_this/
|
||||
// home_object、native 盒直插):按转发表就地晋升进 session(已晋升
|
||||
// 对象的子引用解析到同一克隆,无二次克隆)。只改写存活对象:死对象的
|
||||
// 子引用随对象原地释放(释放路径无后续解引用,不悬垂)。
|
||||
let session_ptrs = std::mem::take(&mut vm.gc_state.session_object_ptrs);
|
||||
let marked_session: Vec<*mut JsObject> = session_ptrs
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&ptr| !ptr.is_null() && unsafe { (*ptr).is_gc_marked() })
|
||||
.collect();
|
||||
vm.rewrite_session_epoch_refs(&marked_session, &mut forwarding);
|
||||
forwarding.clear();
|
||||
vm.gc_state.forwarding = forwarding;
|
||||
|
||||
// 全部 epoch 旧堆区释放:活对象的 vec/native 盒已被克隆深拷贝取代,
|
||||
// 原件原地释放;死对象堆区随本体原地释放。
|
||||
let epoch_freed = Self::free_epoch_object_heap_data(&epoch_ptrs, &session_ptrs);
|
||||
|
||||
// 换新 epoch Bump:旧 arena 全量归还,容量不保留。
|
||||
vm.epoch.reset();
|
||||
|
||||
// ── session 原地非移动 sweep 档 ──
|
||||
|
||||
// 死 session 对象原地释放(堆区 + upvalue 列表)并出表;存活对象不动
|
||||
// arena 槽位——地址不变,免 forwarding/rewrite。
|
||||
let mut survivors = Vec::with_capacity(session_ptrs.len());
|
||||
let mut dead_session = 0u64;
|
||||
let mut session_freed = 0u64;
|
||||
for &ptr in &session_ptrs {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: ptr 来自 session 对象表登记,arena 存活期内有效。
|
||||
if unsafe { (*ptr).is_gc_marked() } {
|
||||
survivors.push(ptr);
|
||||
} else {
|
||||
session_freed += Self::drop_dead_session_object(ptr);
|
||||
dead_session += 1;
|
||||
}
|
||||
}
|
||||
let live_session = survivors.len() as u64;
|
||||
vm.gc_state.session_object_ptrs = survivors;
|
||||
|
||||
// 恢复 mark 位不变量:收集后无残留(残留 true 使下一次 mark DFS 短路漏标)。
|
||||
self.clear_all_marks(vm);
|
||||
|
||||
// 对象口径重算:存活对象 + 存活串 + BigInt(串/BigInt 本路径不动,
|
||||
// 随公式整体重算保持与 strings-only 口径一致)。
|
||||
let mut object_bytes: usize = 0;
|
||||
for &ptr in &vm.gc_state.session_object_ptrs {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: 存活对象在 session 表中,arena 存活期内有效。
|
||||
let obj = unsafe { &*ptr };
|
||||
object_bytes += size_of::<JsObject>() + Self::object_heap_data_bytes(obj) as usize;
|
||||
}
|
||||
let mut string_bytes: usize = 0;
|
||||
for &ptr in &vm.gc_state.session_string_ptrs {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: ptr 在字符串表登记,收尾前有效。
|
||||
string_bytes += size_of::<JsString>() + unsafe { (*ptr).len() };
|
||||
}
|
||||
let bigint_bytes = vm.gc_state.session_bigint_ptrs.borrow().len() * size_of::<num_bigint::BigInt>();
|
||||
vm.gc_state.session_bytes_allocated = object_bytes + string_bytes + bigint_bytes;
|
||||
|
||||
// 抬高下次触发水位:当前分配包络 + 阈值增量——存活包络超阈值时不每指令
|
||||
// 重复触发无死对象可回收的白跑。
|
||||
vm.gc_state.gc_watermark = vm.run_alloc_bytes().saturating_add(vm.gc_state.gc_threshold_cached);
|
||||
vm.gc_state.gc_gate_retry_alloc = 0;
|
||||
|
||||
let live = live_epoch + live_session;
|
||||
let dead = (epoch_ptrs.len() as u64 - live_epoch) + dead_session;
|
||||
let freed_bytes = epoch_freed + session_freed;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
self.total_collections += 1;
|
||||
self.last_collection_duration_us = elapsed.as_micros() as u64;
|
||||
self.max_collection_duration_us = self.max_collection_duration_us.max(self.last_collection_duration_us);
|
||||
self.min_collection_duration_us = self.min_collection_duration_us.min(self.last_collection_duration_us);
|
||||
self.total_bytes_freed = self.total_bytes_freed.saturating_add(freed_bytes);
|
||||
self.total_objects_scanned += (epoch_ptrs.len() + session_ptrs.len()) as u64;
|
||||
self.total_objects_live += live;
|
||||
self.total_objects_dead += dead;
|
||||
self.last_collection_objects_scanned = (epoch_ptrs.len() + session_ptrs.len()) as u64;
|
||||
self.last_collection_objects_live = live;
|
||||
self.last_collection_objects_dead = dead;
|
||||
self.last_collection_bytes_freed = freed_bytes;
|
||||
|
||||
vm_info!(
|
||||
"[GC] in-run cycle #{} end: {} scanned, {} live, {} dead, {:.1}ms, {} bytes freed",
|
||||
self.total_collections,
|
||||
self.last_collection_objects_scanned,
|
||||
self.last_collection_objects_live,
|
||||
self.last_collection_objects_dead,
|
||||
elapsed.as_secs_f64() * 1000.0,
|
||||
freed_bytes,
|
||||
);
|
||||
}
|
||||
|
||||
/// 释放全部 epoch 对象的旧堆区(四处属性/元素向量 + native 状态盒)与独占
|
||||
/// upvalue 列表,返回释放字节数。
|
||||
///
|
||||
/// 调用前提:活 epoch 对象已晋升 session——其 vec/native 盒被克隆深拷贝取代,
|
||||
/// 原件在此恰好释放一次;死对象堆区随本体原地释放。upvalue 列表原件与晋升
|
||||
/// 克隆共享同一 Box(`clone_for_session_epoch` 别名):共享项由 session 对象
|
||||
/// 持有、留待收尾统一释放,此处只放独占项,保证恰好一次。
|
||||
fn free_epoch_object_heap_data(epoch_ptrs: &[*mut JsObject], session_ptrs: &[*mut JsObject]) -> u64 {
|
||||
// 共享集:session 对象表(存活与待死同表)仍持有的 upvalue Box——
|
||||
// 待死 session 对象的 Box 由本收集的原地 sweep 释放,交点恰好一处。
|
||||
let mut shared: std::collections::HashSet<usize> = std::collections::HashSet::new();
|
||||
for &ptr in session_ptrs {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: ptr 来自 session 对象表登记,arena 存活期内有效。
|
||||
let up = unsafe { (*ptr).upvalues } as usize;
|
||||
if up != 0 {
|
||||
shared.insert(up);
|
||||
}
|
||||
}
|
||||
|
||||
let mut seen: std::collections::HashSet<usize> = std::collections::HashSet::new();
|
||||
let mut freed = 0u64;
|
||||
for &ptr in epoch_ptrs {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: epoch 对象表未清空,指向 epoch arena 内合法对象。
|
||||
unsafe {
|
||||
freed += Self::drop_object_heap_data(ptr, false);
|
||||
let up = (*ptr).upvalues as usize;
|
||||
if up == 0 || shared.contains(&up) || !seen.insert(up) {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: up 由 set_upvalues 的 Box::into_raw 分配,去重与共享集
|
||||
// 保证本路径恰好释放一次。
|
||||
drop(Box::from_raw(up as *mut Vec<*mut oxide_types::object::Cell>));
|
||||
(*ptr).upvalues = std::ptr::null_mut();
|
||||
}
|
||||
}
|
||||
freed
|
||||
}
|
||||
|
||||
pub(crate) fn stats_summary(&self) -> String {
|
||||
@@ -1950,4 +2162,171 @@ mod tests {
|
||||
vm.full_reset();
|
||||
assert!(vm.gc_state.session_string_ptrs.is_empty());
|
||||
}
|
||||
|
||||
// ── 执行期两档收集 ──────────────────────────────────────────────────────
|
||||
|
||||
/// 执行期收集有效性:死 epoch 对象随换新 Bump 回收、死 session 对象原地
|
||||
/// 回收出表,活对象晋升 session 后值保持可读。
|
||||
#[test]
|
||||
fn in_run_collection_reclaims_dead_and_keeps_live() {
|
||||
let mut vm = vm_with_threshold(65536);
|
||||
vm.run(&Arc::new(compile("globalThis.keep = { a: 1 }; 0"))).expect("run1");
|
||||
// churn:IIFE 局部数组 + 50 个未入根的函数(函数 session 直分配、
|
||||
// 数组 epoch 分配),IIFE 返回后全部不可达。
|
||||
vm.run(&Arc::new(compile(
|
||||
"(function(){ var t = []; for (var i = 0; i < 50; i++) { t[i] = function() { return i; }; } })(); 0",
|
||||
)))
|
||||
.expect("run2");
|
||||
// run 边界清空执行状态:寄存器文件中的陈旧值不再是执行根,churn 对象
|
||||
// 自此真正不可达。
|
||||
vm.run(&Arc::new(compile("0"))).expect("run3");
|
||||
|
||||
assert!(!vm.gc_state.epoch_object_ptrs.is_empty(), "churn 应留有 epoch 对象");
|
||||
let session_before = vm.session_object_count();
|
||||
assert!(session_before > 0, "churn 应产生 session 直分配函数");
|
||||
|
||||
vm.maybe_collect_in_run();
|
||||
|
||||
assert!(vm.session_gc_stats().total_collections >= 1, "执行期收集应跑一轮");
|
||||
assert!(vm.gc_state.epoch_object_ptrs.is_empty(), "epoch 对象应全部晋升或回收");
|
||||
assert!(vm.session_object_count() < session_before, "死 session 函数应被原地回收");
|
||||
|
||||
// 活对象晋升 session 后值可读。
|
||||
let keep = global_prop_opt(&vm, "keep").expect("keep 应挂在 global");
|
||||
assert!(keep.is_object());
|
||||
let keep_ptr = keep.as_js_object_ptr();
|
||||
assert!(unsafe { (*keep_ptr).is_session_epoch() }, "keep 晋升后应为 session 对象");
|
||||
let a = vm.resolve_property(unsafe { &*keep_ptr }, vm.kernel_core().perm_interner().intern("a").0);
|
||||
assert_eq!(a, Some(JsValue::int(1)));
|
||||
}
|
||||
|
||||
/// for-in 门控双形钉住:活跃形(迭代器在 `vm.iters` 表内)与挂起形
|
||||
/// (生成器在 for-in 内 yield,迭代器搬入状态盒)都须拦下执行期收集——
|
||||
/// 换新 Bump 使 ForInIter body 即时失效;门控开后方可收集。
|
||||
#[test]
|
||||
fn active_and_suspended_for_in_block_in_run_collection() {
|
||||
// 活跃形:迭代器直接压在 vm.iters,O(1) 子句即拦。
|
||||
let mut vm = vm_with_threshold(65536);
|
||||
let dead = plain_object(&mut vm);
|
||||
vm.gc_state.epoch_object_ptrs.push(dead);
|
||||
let iter = vm.epoch.alloc(crate::vm::ForInIter {
|
||||
keys: bumpalo::collections::Vec::new_in(vm.epoch.bump()),
|
||||
index: 0,
|
||||
});
|
||||
vm.iters.push_for_in(iter.cast::<crate::vm::ForInIter<'static>>());
|
||||
let bump_before = vm.epoch.current_id();
|
||||
|
||||
vm.maybe_collect_in_run();
|
||||
assert_eq!(vm.session_gc_stats().total_collections, 0, "活跃 for-in:门控关闭,不收集");
|
||||
assert_eq!(vm.epoch.current_id(), bump_before, "门控关闭:epoch Bump 未换新");
|
||||
assert!(vm.gc_state.epoch_object_ptrs.contains(&dead), "死对象仍在 epoch 表");
|
||||
|
||||
// 迭代器出表后同一调用点即应收集。
|
||||
vm.iters.for_in_iters.pop();
|
||||
vm.maybe_collect_in_run();
|
||||
assert_eq!(vm.session_gc_stats().total_collections, 1, "门控开:应收集");
|
||||
assert!(vm.epoch.current_id() > bump_before, "收集应换新 epoch Bump");
|
||||
assert!(vm.gc_state.epoch_object_ptrs.is_empty(), "死 epoch 对象应随 Bump 回收");
|
||||
|
||||
// 挂起形(拦截):生成器在 for-in 内 yield 后 run 结束,迭代器经
|
||||
// 状态盒持有(vm.iters 已空)——收集点必须拦下,Bump 不得换新。
|
||||
let mut vm = vm_with_threshold(65536);
|
||||
let bump_before = vm.epoch.current_id();
|
||||
vm.run(&Arc::new(compile(
|
||||
"var o = { a: 1, b: 2 }; \
|
||||
function* gen() { for (var k in o) { yield k; } return 'done'; } \
|
||||
var g = gen(); globalThis.g = g; g.next(); 0",
|
||||
)))
|
||||
.expect("run1");
|
||||
assert!(vm.iters.for_in_iters.is_empty(), "挂起时迭代器已搬入状态盒");
|
||||
assert!(vm.suspended_holds_for_in(), "生成器状态盒应持挂起 for-in 迭代器");
|
||||
|
||||
vm.maybe_collect_in_run();
|
||||
assert_eq!(vm.session_gc_stats().total_collections, 0, "挂起 for-in:门控关闭,不收集");
|
||||
assert!(vm.epoch.current_id() == bump_before, "门控关闭:epoch Bump 未换新");
|
||||
// 挂起生成器对象保持原址(未晋升、未释放):状态盒与迭代器一体存活。
|
||||
let gen_in_epoch = vm
|
||||
.gc_state
|
||||
.epoch_object_ptrs
|
||||
.iter()
|
||||
.any(|&p| !p.is_null() && unsafe { (*p).type_tag == JsObject::OBJ_TYPE_GENERATOR });
|
||||
assert!(gen_in_epoch, "挂起生成器应仍在 epoch 表");
|
||||
|
||||
// 挂起形(放行):同一 run 内续跑至 for-in 结束(迭代器释放),
|
||||
// 阈值 1 使指令边界自动触发:挂起期门控关(Bump 不换新),完成后
|
||||
// 门控开、执行期收集放行;yield 值跨收集保持正确。
|
||||
let mut vm = vm_with_threshold(1);
|
||||
vm.run(&Arc::new(compile(
|
||||
"var o = { a: 1, b: 2 }; \
|
||||
function* gen() { for (var k in o) { yield k; } return 'done'; } \
|
||||
var g = gen(); globalThis.g = g; \
|
||||
globalThis.v1 = g.next().value; globalThis.v2 = g.next().value; g.next(); 0",
|
||||
)))
|
||||
.expect("run2");
|
||||
assert_eq!(vm.lookup_str(global_prop_opt(&vm, "v1").expect("v1")), Some("a".to_string()));
|
||||
assert_eq!(vm.lookup_str(global_prop_opt(&vm, "v2").expect("v2")), Some("b".to_string()));
|
||||
assert!(!vm.suspended_holds_for_in(), "for-in 结束后状态盒不应再持迭代器");
|
||||
assert!(vm.session_gc_stats().total_collections >= 1, "完成后门控开:执行期收集应放行");
|
||||
|
||||
// 生成器对象跨收集仍为合法生成器(晋升 session 或保持 epoch 完好)。
|
||||
let g = global_prop_opt(&vm, "g").expect("g 应挂在 global");
|
||||
assert!(unsafe { (*g.as_js_object_ptr()).is_generator_obj() }, "收集后生成器对象应完好");
|
||||
}
|
||||
|
||||
/// 非移动不变量:执行期收集的存活 session 对象地址不变(元素堆区亦不
|
||||
/// 换盒)——用户可观察 identity 不分裂,免 forwarding/rewrite。
|
||||
#[test]
|
||||
fn in_run_collection_keeps_live_addresses_stable() {
|
||||
let mut vm = vm_with_threshold(65536);
|
||||
vm.run(&Arc::new(compile("globalThis.o = { a: [1, 2, 3], f: function() { return 1; } }; 0")))
|
||||
.expect("run1");
|
||||
|
||||
// 第一轮执行期收集:o 及其 epoch 子引用晋升 session。
|
||||
vm.maybe_collect_in_run();
|
||||
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 对象");
|
||||
let a_ptr = vm
|
||||
.resolve_property(unsafe { &*o_ptr }, vm.kernel_core().perm_interner().intern("a").0)
|
||||
.expect("o.a")
|
||||
.as_js_object_ptr();
|
||||
let f_ptr = vm
|
||||
.resolve_property(unsafe { &*o_ptr }, vm.kernel_core().perm_interner().intern("f").0)
|
||||
.expect("o.f")
|
||||
.as_js_object_ptr();
|
||||
let a_elems = unsafe { (*a_ptr).array_elements_raw() };
|
||||
|
||||
// 第二轮 churn + 收集:死对象回收,存活对象与堆区地址保持不变。
|
||||
vm.run(&Arc::new(compile(
|
||||
"(function(){ var t = []; for (var i = 0; i < 50; i++) { t[i] = { x: i }; } })(); 0",
|
||||
)))
|
||||
.expect("run2");
|
||||
// run 边界清空执行状态:churn 局部(t 数组与 50 个对象)自此不可达。
|
||||
vm.run(&Arc::new(compile("0"))).expect("run3");
|
||||
vm.maybe_collect_in_run();
|
||||
assert!(vm.session_gc_stats().total_collections >= 2, "两轮收集均应执行");
|
||||
|
||||
assert_eq!(global_prop_opt(&vm, "o").expect("o").as_js_object_ptr(), o_ptr, "存活对象地址不变");
|
||||
assert_eq!(
|
||||
vm.resolve_property(unsafe { &*o_ptr }, vm.kernel_core().perm_interner().intern("a").0)
|
||||
.expect("o.a")
|
||||
.as_js_object_ptr(),
|
||||
a_ptr,
|
||||
"数组对象地址不变"
|
||||
);
|
||||
assert_eq!(
|
||||
vm.resolve_property(unsafe { &*o_ptr }, vm.kernel_core().perm_interner().intern("f").0)
|
||||
.expect("o.f")
|
||||
.as_js_object_ptr(),
|
||||
f_ptr,
|
||||
"函数对象地址不变"
|
||||
);
|
||||
assert_eq!(unsafe { (*a_ptr).array_elements_raw() }, a_elems, "元素堆区不换盒");
|
||||
assert_eq!(
|
||||
vm.resolve_property(unsafe { &*a_ptr }, vm.kernel_core().perm_interner().intern("1").0)
|
||||
.unwrap_or(JsValue::int(-1)),
|
||||
JsValue::int(2),
|
||||
"元素值跨收集可读"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -964,13 +964,62 @@ impl Vm {
|
||||
self.gc_state.session_gc = session_gc;
|
||||
}
|
||||
|
||||
/// 执行期完整 GC(对象+字符串)的 dispatch 安全点入口:按水位判定触发。
|
||||
/// 仅在 native_call_depth == 0 的指令边界调用——此时无 builtin 局部裸指针,
|
||||
/// 对象搬移安全。预留给 17.3b(对象侧执行期触发)安全点审计后使用。
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn maybe_collect_session_gc_at_dispatch(&mut self) {
|
||||
/// 门控子句二:三型状态盒(生成器/异步函数/异步生成器)的
|
||||
/// `suspended.for_in_iters` 是否非空。
|
||||
///
|
||||
/// ForInIter body 分配于 epoch arena,收集换新 Bump 即时失效在表迭代器;
|
||||
/// 挂起帧把迭代器搬入状态盒(`vm.iters` 不可见),故须逐状态盒扫描。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 扫描 epoch + session 两份对象表——挂起状态盒宿主对象可能尚未晋升;
|
||||
/// - 挂起帧的 keys 经状态盒边收为 GC 根(被枚举对象不误释放),悬的是
|
||||
/// 迭代器 body,故按指针扫 `for_in_iters`;
|
||||
/// - 保守口径:死对象的状态盒同样计入(持挂起 for-in 的 run 放弃执行期
|
||||
/// 收集),正确性优先于收益;
|
||||
/// - 新增挂起态持有者须在此登记。
|
||||
pub(crate) fn suspended_holds_for_in(&self) -> bool {
|
||||
let holds = |&ptr: &*mut JsObject| {
|
||||
if ptr.is_null() {
|
||||
return false;
|
||||
}
|
||||
// SAFETY: 对象表登记的指针,arena 存活期内有效。
|
||||
let obj = unsafe { &*ptr };
|
||||
match obj.type_tag {
|
||||
JsObject::OBJ_TYPE_GENERATOR => crate::generator::generator_holds_suspended_for_in(obj),
|
||||
JsObject::OBJ_TYPE_ASYNC => crate::async_func::async_holds_suspended_for_in(obj),
|
||||
JsObject::OBJ_TYPE_ASYNC_GENERATOR => {
|
||||
crate::async_generator::async_generator_holds_suspended_for_in(obj)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
self.gc_state.epoch_object_ptrs.iter().any(holds) || self.gc_state.session_object_ptrs.iter().any(holds)
|
||||
}
|
||||
|
||||
/// 执行期两档收集的 dispatch 安全点入口:仅在循环顶
|
||||
/// (`native_call_depth == 0`,无 builtin 局部裸指针、dispatch 未重入)
|
||||
/// 由触发块调用;先门控后收集。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 子句一(活跃 for-in,O(1))未过:免费重试,不设锚;
|
||||
/// - 子句二(状态盒扫描,O(对象数))未过:重扫按包络增长一个阈值设锚,
|
||||
/// 避免持挂起 for-in 的 run 每指令边界重扫;
|
||||
/// - 门控过但无对象可回收时仍跑一轮(mark + 换新 Bump),水位同点抬高,
|
||||
/// 触发间距由包络增量控制。
|
||||
pub(crate) fn maybe_collect_in_run(&mut self) {
|
||||
if !self.iters.for_in_iters.is_empty() {
|
||||
return;
|
||||
}
|
||||
if self.gc_state.gc_gate_retry_alloc > self.run_alloc_bytes() {
|
||||
return;
|
||||
}
|
||||
if self.suspended_holds_for_in() {
|
||||
self.gc_state.gc_gate_retry_alloc =
|
||||
self.run_alloc_bytes().saturating_add(self.gc_state.gc_threshold_cached);
|
||||
return;
|
||||
}
|
||||
let mut session_gc = std::mem::take(&mut self.gc_state.session_gc);
|
||||
session_gc.maybe_collect_gc(self);
|
||||
session_gc.collect_in_run(self);
|
||||
self.gc_state.session_gc = session_gc;
|
||||
}
|
||||
|
||||
@@ -1651,6 +1700,11 @@ impl Vm {
|
||||
if bytes >= self.gc_state.string_gc_watermark {
|
||||
self.maybe_collect_session_strings();
|
||||
}
|
||||
// 执行期两档收集:O(1) 包络超触发水位 → 门控(活跃/挂起 for-in)
|
||||
// + epoch 晋升收集 + session 原地 sweep(边界契约见 maybe_collect_in_run)。
|
||||
if alloc >= self.gc_state.gc_watermark {
|
||||
self.maybe_collect_in_run();
|
||||
}
|
||||
}
|
||||
if let Some(max_steps) = max_steps {
|
||||
if steps > max_steps {
|
||||
|
||||
@@ -49,14 +49,14 @@ pub(crate) struct GcState {
|
||||
/// 触发无死串可回收的白跑,且保证触发点恒在无 builtin 局部活值的边界。
|
||||
pub(crate) string_gc_watermark: usize,
|
||||
/// 缓存的 GC 阈值(从 config 读一次),热路径只做 usize 比较,免 Arc 解引用。
|
||||
/// 预留给 17.3b(对象侧执行期触发)安全点审计后使用。
|
||||
#[allow(dead_code)]
|
||||
pub(crate) gc_threshold_cached: usize,
|
||||
/// 执行期完整 GC(对象+字符串)的触发水位:本次收集后的存活字节 + 阈值增量。
|
||||
/// 仅当账目超过水位才在指令边界触发完整回收——存活对象超阈值时不每指令重复
|
||||
/// 触发无死对象可回收的白跑。预留给 17.3b 安全点审计后使用。
|
||||
#[allow(dead_code)]
|
||||
/// 执行期两档收集(epoch 晋升 + session 原地 sweep)的触发水位:本次
|
||||
/// 收集后的分配包络 + 阈值增量。仅当 `run_alloc_bytes` 超过水位才在指令
|
||||
/// 边界触发——存活包络超阈值时不每指令重复触发无死对象可回收的白跑。
|
||||
pub(crate) gc_watermark: usize,
|
||||
/// 门控未过(挂起态持有 for-in)后的重扫锚:包络再增长一个阈值才重扫
|
||||
/// 三型状态盒(O(对象数) 扫描),避免持挂起 for-in 的 run 每指令边界重扫。
|
||||
pub(crate) gc_gate_retry_alloc: usize,
|
||||
pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ impl Vm {
|
||||
string_gc_watermark: gc_threshold,
|
||||
gc_threshold_cached: gc_threshold,
|
||||
gc_watermark: gc_threshold,
|
||||
gc_gate_retry_alloc: 0,
|
||||
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
|
||||
},
|
||||
symbols: SymbolState {
|
||||
@@ -235,6 +236,7 @@ impl Vm {
|
||||
string_gc_watermark: gc_threshold,
|
||||
gc_threshold_cached: gc_threshold,
|
||||
gc_watermark: gc_threshold,
|
||||
gc_gate_retry_alloc: 0,
|
||||
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
|
||||
},
|
||||
symbols: SymbolState {
|
||||
@@ -356,6 +358,9 @@ impl Vm {
|
||||
self.gc_state.session_bytes_peak = 0;
|
||||
self.gc_state.run_alloc_peak = 0;
|
||||
self.gc_state.string_gc_watermark = self.kernel_core.config().session_gc_threshold;
|
||||
// 两档收集水位与门控重扫锚同点复位(同式:阈值增量起算)。
|
||||
self.gc_state.gc_watermark = self.gc_state.gc_threshold_cached;
|
||||
self.gc_state.gc_gate_retry_alloc = 0;
|
||||
self.gc_state.session_gc = crate::session_gc::SessionGc::new();
|
||||
self.symbols.reset();
|
||||
self.root_reg_limit = 0;
|
||||
@@ -508,6 +513,9 @@ impl Vm {
|
||||
self.gc_state.epoch_object_ptrs.clear();
|
||||
// 单 run 分配包络按 run 边界重起算(与 run_alloc_bytes 起算口径同源)。
|
||||
self.gc_state.run_alloc_peak = 0;
|
||||
// 两档收集水位与包络同起算:旧 run 的存活包络不延续到新 run 的触发判定。
|
||||
self.gc_state.gc_watermark = self.gc_state.gc_threshold_cached;
|
||||
self.gc_state.gc_gate_retry_alloc = 0;
|
||||
self.root_reg_limit = 0;
|
||||
self.active_reg_limit = 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user