refactor: 收集路径原地化退役移动式sweep与门控

This commit is contained in:
2026-10-01 09:06:23 +00:00
parent 1711ae948b
commit 5c8699733d
14 changed files with 309 additions and 573 deletions
+4 -3
View File
@@ -123,7 +123,7 @@ impl Vm {
// 返回顶层后检查恢复,存活值此时已回拷为执行根。账目未超水位时仅
// 少量字段比较。
if self.native_call_depth == 0 {
// 宿主 `$262.gc()` 置位的强制收集在此边界消费:移动式 sweep
// 宿主 `$262.gc()` 置位的强制收集在此边界消费:完整收集
// 唯一安全点(无在途 dispatch 局部值),与正常触发路径同一入口。
if self.gc_state.pending_forced_collect {
self.gc_state.pending_forced_collect = false;
@@ -142,8 +142,9 @@ 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)。
// 执行期原地 sweep 收集:O(1) 包络超触发水位 → 收集(无门控:
// 迭代器键引用经根收集标活,原地释放不搬移存活对象;
// 边界契约见 maybe_collect_in_run)。
if alloc >= self.gc_state.gc_watermark {
self.maybe_collect_in_run();
}
+8 -51
View File
@@ -239,60 +239,17 @@ impl Vm {
self.gc_state.session_gc = session_gc;
}
/// 门控子句二:三型状态盒(生成器/异步函数/异步生成器)的
/// `suspended.for_in_iters` 是否非空。
///
/// 门控是保守门:for-in 迭代器活跃/挂起期间拦下执行期收集;
/// 挂起帧把迭代器搬入状态盒(`vm.iters` 不可见),故须逐状态盒扫描。
///
/// # 边界与前提
/// - 扫描 session 对象表——挂起状态盒宿主对象在表内;
/// - 挂起帧的 keys 经状态盒边收为 GC 根(被枚举对象不误释放),迭代器
/// 体是堆上 Box,故按指针扫 `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: 对象表登记的指针,存活期内有效。
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.session_object_ptrs.iter().any(holds)
}
/// 执行期两档收集的 dispatch 安全点入口:仅在循环顶
/// 执行期原地 sweep 收集的 dispatch 安全点入口:仅在循环顶
/// (`native_call_depth == 0`,无 builtin 局部裸指针、dispatch 未重入)
/// 由触发块调用;先门控后收集。
/// 由触发块在水位命中后调用,无门控直接收集。
///
/// # 边界与前提
/// - 子句一(活跃 for-in,O(1))未过:免费重试,不设锚;
/// - 子句二(状态盒扫描,O(对象数))未过:重扫按包络增长一个阈值设锚,
/// 避免持挂起 for-in 的 run 每指令边界重扫;
/// - 门控过但无对象可回收时仍跑一轮(mark + 换新 Bump),水位同点抬高,
/// - for-in 迭代器体是堆上 Box:活跃形经 VM 表、挂起形经状态盒边进入
/// 根收集,键引用被 mark 标活,原地清扫不搬移对象、不误释放,
/// 无需拦截;
/// - 无对象可回收时仍跑一轮(mark + 原地清扫),水位同点抬高,
/// 触发间距由包络增量控制。
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.collect_in_run(self);
self.gc_state.session_gc = session_gc;
@@ -373,10 +330,10 @@ impl Vm {
bytes
}
/// 无条件执行一次完整 session GC(mark + 移动式 sweep + 串/BigInt 清扫)。
/// 无条件执行一次完整 session GC(mark + 对象原地清扫 + 串/BigInt 清扫)。
///
/// # 副作用
/// - 存活对象复制进新 session arena,全部根与原生盒按转发表重写;
/// - 存活对象地址不变、根与原生盒免改写;
/// `session_bytes_allocated` 重置为清扫后的存活字节。
///
/// # 注意事项