refactor: for-in 迭代器体脱离 epoch arena 改堆上 Box 独占释放

This commit is contained in:
2026-09-29 16:10:15 +00:00
parent c067dcce21
commit c671a6cfb0
14 changed files with 117 additions and 55 deletions
+1 -1
View File
@@ -703,7 +703,7 @@ fn async_state_mut(obj: &JsObject) -> Option<&mut AsyncState> {
}
/// 异步挂起帧是否持有 for-in 迭代器:执行期收集门控按指针扫描
/// (ForInIter body 分配于 epoch arena,换新 Bump 即时失效在表迭代器)。
/// (迭代器体是堆上 Box,门控是保守门)。
pub(crate) fn async_holds_suspended_for_in(obj: &JsObject) -> bool {
let Some(state) = async_state_mut(obj) else {
return false;
+1 -1
View File
@@ -1677,7 +1677,7 @@ fn async_gen_state_mut(obj: &JsObject) -> Option<&mut AsyncGeneratorState> {
}
/// 异步生成器挂起帧是否持有 for-in 迭代器:执行期收集门控按指针扫描
/// (ForInIter body 分配于 epoch arena,换新 Bump 即时失效在表迭代器)。
/// (迭代器体是堆上 Box,门控是保守门)。
pub(crate) fn async_generator_holds_suspended_for_in(obj: &JsObject) -> bool {
let Some(state) = async_gen_state_mut(obj) else {
return false;
+1 -1
View File
@@ -965,7 +965,7 @@ fn generator_state_mut(obj: &JsObject) -> Option<&mut GeneratorState> {
}
/// 生成器挂起帧是否持有 for-in 迭代器:执行期收集门控按指针扫描
/// (ForInIter body 分配于 epoch arena,换新 Bump 即时失效在表迭代器)。
/// (迭代器体是堆上 Box,门控是保守门)。
pub(crate) fn generator_holds_suspended_for_in(obj: &JsObject) -> bool {
let Some(state) = generator_state_mut(obj) else {
return false;
+2 -2
View File
@@ -1040,8 +1040,8 @@ impl SessionGc {
/// identity 不分裂。
///
/// # 边界与前提
/// - 调用方已完成门控(无活跃/挂起 for-in):ForInIter body 分配于 epoch
/// arena,换新 Bump 即时失效在表迭代器;
/// - 调用方已完成门控(无活跃/挂起 for-in):门控是保守门,迭代器体
/// 是堆上 Box,收集不搬移迭代器体;
/// - 调用点为 dispatch 安全点(`native_call_depth == 0`),无在途 builtin
/// 局部裸指针、dispatch 未重入。
///
+6 -6
View File
@@ -1730,18 +1730,18 @@ fn in_run_collection_reclaims_dead_and_keeps_live() {
/// for-in 门控双形钉住:活跃形(迭代器在 `vm.iters` 表内)与挂起形
/// (生成器在 for-in 内 yield,迭代器搬入状态盒)都须拦下执行期收集——
/// 换新 Bump 使 ForInIter body 即时失效;门控开后方可收集。
/// 门控是保守门,迭代器体为堆上 Box;门控开后方可收集。
#[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()),
let iter = Box::into_raw(Box::new(crate::vm::ForInIter {
keys: Vec::new(),
index: 0,
});
vm.iters.push_for_in(iter.cast::<crate::vm::ForInIter<'static>>());
}));
vm.iters.push_for_in(iter);
let bump_before = vm.epoch.current_id();
vm.maybe_collect_in_run();
@@ -1750,7 +1750,7 @@ fn active_and_suspended_for_in_block_in_run_collection() {
assert!(vm.gc_state.epoch_object_ptrs.contains(&dead), "死对象仍在 epoch 表");
// 迭代器出表后同一调用点即应收集。
vm.iters.for_in_iters.pop();
vm.iters.pop_for_in();
vm.maybe_collect_in_run();
assert_eq!(vm.session_gc_stats().total_collections, 1, "门控开:应收集");
assert!(vm.epoch.current_id() > bump_before, "收集应换新 epoch Bump");
+32 -4
View File
@@ -30,7 +30,7 @@ pub(crate) struct SuspendedFrame {
pub save_stack: Vec<JsValue>,
pub cell_stack: Vec<Vec<*mut Cell>>,
pub try_stack: Vec<TryHandler>,
pub for_in_iters: Vec<*mut ForInIter<'static>>,
pub for_in_iters: Vec<*mut ForInIter>,
pub for_of_iters: Vec<ForOfEntry>,
/// `yield*` 委托中的内层迭代器;异步函数恒为 None。
pub delegated_iterator: Option<JsValue>,
@@ -235,7 +235,7 @@ impl SuspendedFrame {
if iter.is_null() {
continue;
}
// SAFETY: for_in_iters 存放由当前 VM epoch 拥有的存活迭代器指针。
// SAFETY: for_in_iters 存放堆上迭代器体,状态盒独占持有。
for (v, _si) in unsafe { (*(*iter)).keys.iter() } {
f(*v);
}
@@ -316,7 +316,7 @@ impl SuspendedFrame {
if iter.is_null() {
continue;
}
// SAFETY: for_in_iters 存放由当前 VM epoch 拥有的存活迭代器指针。
// SAFETY: for_in_iters 存放堆上迭代器体,状态盒独占持有。
for (v, _si) in unsafe { (*(*iter)).keys.iter_mut() } {
*v = rewrite(*v);
}
@@ -362,7 +362,18 @@ impl SuspendedFrame {
save_stack: self.save_stack.iter().copied().map(&mut rewrite).collect(),
cell_stack: self.cell_stack.clone(),
try_stack: self.try_stack.clone(),
for_in_iters: self.for_in_iters.clone(),
for_in_iters: self.for_in_iters.iter().map(|&iter| {
if iter.is_null() {
return iter;
}
// SAFETY: 指针是堆上迭代器体,原件状态盒独占持有;克隆新建
// 独立体,原件与克隆各持自有体,恰好各释放一次。
let body = unsafe { &*iter };
Box::into_raw(Box::new(ForInIter {
keys: body.keys.iter().map(|(v, si)| (rewrite(*v), *si)).collect(),
index: body.index,
}))
}).collect(),
for_of_iters: self
.for_of_iters
.iter()
@@ -425,6 +436,23 @@ impl SuspendedFrame {
}
}
impl Drop for SuspendedFrame {
/// 释放状态盒持有的 for-in 迭代器体:体是堆上 `Box`,每个状态盒独占
/// 持有自身体,随状态盒释放逐条释放(null 跳过)。restore 已把向量
/// 移出时字段为空,Drop 不再触碰,无双放。
fn drop(&mut self) {
for iter in self.for_in_iters.drain(..) {
if iter.is_null() {
continue;
}
// SAFETY: 指针是堆上迭代器体,状态盒独占持有,释放恰好一次。
unsafe {
drop(Box::from_raw(iter));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
+4 -2
View File
@@ -87,11 +87,13 @@ pub struct CallFrame {
/// 一次 `for-in` 迭代的游标:已收集的 key 列表与当前下标。
///
/// 体是堆上 `Box`:迭代器表与挂起状态盒各独占持有体,出表/状态盒释放时
/// 逐条释放。
/// 每个 key 与它的 intern id 配对保存,使整型下标 key 无需重新 intern 即可排到字符串 key 之前。
pub struct ForInIter<'bump> {
pub struct ForInIter {
/// 每个 key 与其字符串 intern id 配对,使 for-in 排序时整型下标 key 无需
/// 重新 intern 即可排到字符串 key 之前。
pub keys: bumpalo::collections::Vec<'bump, (JsValue, u32)>,
pub keys: Vec<(JsValue, u32)>,
pub index: usize,
}
+5 -5
View File
@@ -96,7 +96,7 @@ impl Vm {
if iter.is_null() {
continue;
}
// SAFETY: for_in_iters 存放由当前 VM epoch 拥有的存活迭代器指针。
// SAFETY: for_in_iters 存放堆上迭代器体,VM 表独占持有。
unsafe {
for (v, _si) in (*(*iter)).keys.iter() {
f(*v);
@@ -200,7 +200,7 @@ impl Vm {
if iter.is_null() {
continue;
}
// SAFETY: for_in_iters 存放由当前 VM epoch 拥有的存活迭代器指针。
// SAFETY: for_in_iters 存放堆上迭代器体,VM 表独占持有。
unsafe {
for (v, _si) in (*(*iter)).keys.iter_mut() {
*v = rewrite(*v);
@@ -242,13 +242,13 @@ impl Vm {
/// 门控子句二:三型状态盒(生成器/异步函数/异步生成器)的
/// `suspended.for_in_iters` 是否非空。
///
/// ForInIter body 分配于 epoch arena,收集换新 Bump 即时失效在表迭代器;
/// 门控是保守门:for-in 迭代器活跃/挂起期间拦下执行期收集;
/// 挂起帧把迭代器搬入状态盒(`vm.iters` 不可见),故须逐状态盒扫描。
///
/// # 边界与前提
/// - 扫描 epoch + session 两份对象表——挂起状态盒宿主对象可能尚未晋升;
/// - 挂起帧的 keys 经状态盒边收为 GC 根(被枚举对象不误释放),悬的是
/// 迭代器 body,故按指针扫 `for_in_iters`;
/// - 挂起帧的 keys 经状态盒边收为 GC 根(被枚举对象不误释放),迭代器
/// 体是堆上 Box,故按指针扫 `for_in_iters`;
/// - 保守口径:死对象的状态盒同样计入(持挂起 for-in 的 run 放弃执行期
/// 收集),正确性优先于收益;
/// - 新增挂起态持有者须在此登记。
+18 -1
View File
@@ -32,7 +32,7 @@ pub(crate) struct InlineSyncState {
pub(crate) pending_exception: Option<JsValue>,
pub(crate) pending_error_kind: Option<&'static str>,
pub(crate) pending_completion: Option<Completion>,
pub(crate) for_in_iters: Vec<*mut ForInIter<'static>>,
pub(crate) for_in_iters: Vec<*mut ForInIter>,
pub(crate) for_of_iters: Vec<ForOfEntry>,
pub(crate) saved_bytecode_stack: Vec<Arc<[opcode::Instr]>>,
pub(crate) saved_immutables_stack: Vec<*const [JsValue]>,
@@ -62,3 +62,20 @@ pub(crate) struct InlineSyncState {
pub(crate) active_flat_id: u32,
pub(crate) active_table_gen: u32,
}
impl Drop for InlineSyncState {
/// 释放快照持有的 for-in 迭代器体:体是堆上 `Box`,快照独占持有自身体,
/// drop 时逐条释放(null 跳过)。restore 是字段直移,移出后字段为空,
/// Drop 不再触碰,无双放。
fn drop(&mut self) {
for iter in self.for_in_iters.drain(..) {
if iter.is_null() {
continue;
}
// SAFETY: 指针是堆上迭代器体,快照独占持有,释放恰好一次。
unsafe {
drop(Box::from_raw(iter));
}
}
}
}
+6 -10
View File
@@ -464,10 +464,8 @@ impl Vm {
let mut obj_val = self.regs[a];
if obj_val.is_null() || obj_val.is_undefined() {
// null/undefined 枚举不到任何键——是空 for-in,而非 TypeError。
let keys_vec: bumpalo::collections::Vec<(JsValue, u32)> =
bumpalo::collections::Vec::new_in(self.epoch.bump());
let iter = self.epoch.alloc(ForInIter { keys: keys_vec, index: 0 });
self.iters.push_for_in(iter.cast::<ForInIter<'static>>());
let iter = Box::into_raw(Box::new(ForInIter { keys: Vec::new(), index: 0 }));
self.iters.push_for_in(iter);
return Ok(());
}
if !obj_val.is_object() {
@@ -595,12 +593,10 @@ impl Vm {
}
});
// std Vec 建完后迁入 bump 区,避免枚举循环期间对 self 的 &mut 借用
// 与 bump 借用冲突。
let keys_bump: bumpalo::collections::Vec<(JsValue, u32)> =
bumpalo::collections::Vec::from_iter_in(keys_vec, self.epoch.bump());
let iter = self.epoch.alloc(ForInIter { keys: keys_bump, index: 0 });
self.iters.push_for_in(iter.cast::<ForInIter<'static>>());
// std Vec 直接作为迭代器体;体是堆上 Box,生命周期随迭代器表与
// 挂起状态盒,出表/状态盒释放时逐条释放。
let iter = Box::into_raw(Box::new(ForInIter { keys: keys_vec, index: 0 }));
self.iters.push_for_in(iter);
Ok(())
}
+13 -14
View File
@@ -146,7 +146,7 @@ macro_rules! inline_save_field {
macro_rules! inline_restore_field {
($recv:ident, $saved:ident, regs, boxed_window) => {
$recv.regs[..$saved.regs.len()].copy_from_slice(&$saved.regs);
$recv.inline_reg_pool = Some($saved.regs.into_vec());
$recv.inline_reg_pool = Some(std::mem::take(&mut $saved.regs).into_vec());
};
($recv:ident, $saved:ident, saved_this, copy) => {
$recv.regs[254] = $saved.saved_this
@@ -158,7 +158,7 @@ macro_rules! inline_restore_field {
$recv.pc = $saved.pc
};
($recv:ident, $saved:ident, bytecode, move_field) => {
$recv.bytecode = $saved.bytecode
$recv.bytecode = std::mem::take(&mut $saved.bytecode)
};
($recv:ident, $saved:ident, active_immutables, copy) => {
$recv.active_immutables = $saved.active_immutables
@@ -170,10 +170,10 @@ macro_rules! inline_restore_field {
$recv.root_reg_limit = $saved.root_reg_limit
};
($recv:ident, $saved:ident, try_stack, move_field) => {
$recv.try_stack = $saved.try_stack
$recv.try_stack = std::mem::take(&mut $saved.try_stack)
};
($recv:ident, $saved:ident, frames, frames_values) => {
$recv.frames = $saved.frames
$recv.frames = std::mem::take(&mut $saved.frames)
};
($recv:ident, $saved:ident, exception_value, opt_take) => {
$recv.exception_value = $saved.exception_value
@@ -188,25 +188,25 @@ macro_rules! inline_restore_field {
$recv.pending_completion = $saved.pending_completion
};
($recv:ident, $saved:ident, for_in_iters, for_in_keys) => {
$recv.iters.for_in_iters = $saved.for_in_iters
$recv.iters.for_in_iters = std::mem::take(&mut $saved.for_in_iters)
};
($recv:ident, $saved:ident, for_of_iters, iter_take) => {
$recv.iters.for_of_iters = $saved.for_of_iters
$recv.iters.for_of_iters = std::mem::take(&mut $saved.for_of_iters)
};
($recv:ident, $saved:ident, saved_bytecode_stack, move_field) => {
$recv.saved_bytecode_stack = $saved.saved_bytecode_stack
$recv.saved_bytecode_stack = std::mem::take(&mut $saved.saved_bytecode_stack)
};
($recv:ident, $saved:ident, saved_immutables_stack, move_field) => {
$recv.saved_immutables_stack = $saved.saved_immutables_stack
$recv.saved_immutables_stack = std::mem::take(&mut $saved.saved_immutables_stack)
};
($recv:ident, $saved:ident, save_stack, move_field) => {
$recv.save_stack = $saved.save_stack
$recv.save_stack = std::mem::take(&mut $saved.save_stack)
};
($recv:ident, $saved:ident, spill_stack, move_field) => {
$recv.spill_stack = $saved.spill_stack
$recv.spill_stack = std::mem::take(&mut $saved.spill_stack)
};
($recv:ident, $saved:ident, cell_stack, move_field) => {
$recv.cell_stack = $saved.cell_stack
$recv.cell_stack = std::mem::take(&mut $saved.cell_stack)
};
($recv:ident, $saved:ident, inline_callee, opt_copy) => {
$recv.inline_callee = $saved.inline_callee
@@ -336,7 +336,7 @@ impl Vm {
/// 把 [`save_inline_state`] 保存的状态恢复回 VM。窗口回拷 + `regs[254]/[255]`
/// 单回,窗口外寄存器 callee 未触碰无需恢复。
pub(crate) fn restore_inline_state(&mut self, saved: Box<InlineSyncState>) {
pub(crate) fn restore_inline_state(&mut self, mut saved: Box<InlineSyncState>) {
vm_trace!("restore_inline_state: pc={}", saved.pc);
// 固化不变量:内联 state-swap 边界不携带在途异步逃出(与 save 侧同)。
debug_assert!(self.pending_async_escape.is_none());
@@ -363,8 +363,7 @@ impl Vm {
self.active_reg_limit = 1;
self.root_reg_limit = 1;
self.try_stack.clear();
self.iters.for_in_iters.clear();
self.iters.for_of_iters.clear();
self.iters.reset();
self.spill_stack.clear();
self.save_stack.clear();
self.saved_bytecode_stack.clear();
+21 -5
View File
@@ -175,25 +175,41 @@ pub(crate) struct ForOfEntry {
/// for-in / for-of 的活跃迭代器状态。
pub(crate) struct IterState {
pub(crate) for_in_iters: Vec<*mut ForInIter<'static>>,
pub(crate) for_in_iters: Vec<*mut ForInIter>,
pub(crate) for_of_iters: Vec<ForOfEntry>,
}
impl IterState {
pub(crate) fn reset(&mut self) {
self.for_in_iters.clear();
for iter in self.for_in_iters.drain(..) {
if iter.is_null() {
continue;
}
// SAFETY: 指针是堆上迭代器体,表独占持有,清表释放恰好一次。
unsafe {
drop(Box::from_raw(iter));
}
}
self.for_of_iters.clear();
}
pub(crate) fn push_for_in(&mut self, iter: *mut ForInIter<'static>) {
pub(crate) fn push_for_in(&mut self, iter: *mut ForInIter) {
self.for_in_iters.push(iter);
}
pub(crate) fn pop_for_in(&mut self) {
self.for_in_iters.pop();
if let Some(iter) = self.for_in_iters.pop() {
if iter.is_null() {
return;
}
// SAFETY: 指针是堆上迭代器体,表独占持有,出表释放恰好一次。
unsafe {
drop(Box::from_raw(iter));
}
}
}
pub(crate) fn last_for_in(&self) -> *mut ForInIter<'static> {
pub(crate) fn last_for_in(&self) -> *mut ForInIter {
self.for_in_iters.last().copied().unwrap_or(std::ptr::null_mut())
}
+3
View File
@@ -428,6 +428,9 @@ impl Vm {
/// 重置,本函数只释放手工管理的 Box 指针(属性向量、各原生盒、串、BigInt、cell)。
/// 原生盒在 GC 搬移/晋升时已深拷贝为单所有权,此处恰好释放一次。
pub(crate) fn teardown_session_heap_data(&mut self) {
// 迭代器体是堆上 Box:收尾路径(full_reset 与 Drop 共用)逐条释放,
// 防 Vm 直接 drop 时表内残留体泄漏。
self.iters.reset();
// upvalue 列表先于对象表清空释放:去重枚举依赖两份对象表尚存。
self.free_session_upvalues();
self.free_epoch_object_heap_data();
+4 -3
View File
@@ -847,9 +847,10 @@ fn reset_clears_runtime_state_like_rerun() {
upvalues: std::ptr::slice_from_raw_parts(std::ptr::null(), 0),
});
vm.save_stack.push(JsValue::undefined());
vm.iters
.for_in_iters
.push(std::ptr::dangling_mut::<crate::vm::ForInIter<'static>>());
vm.iters.for_in_iters.push(Box::into_raw(Box::new(crate::vm::ForInIter {
keys: Vec::new(),
index: 0,
})));
vm.iters.for_of_iters.push(crate::vm_state::ForOfEntry {
iterator: JsValue::undefined(),
last_result: JsValue::undefined(),