diff --git a/crates/oxide_vm/src/async_func.rs b/crates/oxide_vm/src/async_func.rs index 5ba0b3b1..4077ef60 100644 --- a/crates/oxide_vm/src/async_func.rs +++ b/crates/oxide_vm/src/async_func.rs @@ -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; diff --git a/crates/oxide_vm/src/async_generator.rs b/crates/oxide_vm/src/async_generator.rs index 62ce5ce7..cd4c5d0a 100644 --- a/crates/oxide_vm/src/async_generator.rs +++ b/crates/oxide_vm/src/async_generator.rs @@ -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; diff --git a/crates/oxide_vm/src/generator.rs b/crates/oxide_vm/src/generator.rs index 5062b3ef..36eee0be 100644 --- a/crates/oxide_vm/src/generator.rs +++ b/crates/oxide_vm/src/generator.rs @@ -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; diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index 81fec05b..c51c87cd 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -1040,8 +1040,8 @@ impl SessionGc { /// identity 不分裂。 /// /// # 边界与前提 - /// - 调用方已完成门控(无活跃/挂起 for-in):ForInIter body 分配于 epoch - /// arena,换新 Bump 即时失效在表迭代器; + /// - 调用方已完成门控(无活跃/挂起 for-in):门控是保守门,迭代器体 + /// 是堆上 Box,收集不搬移迭代器体; /// - 调用点为 dispatch 安全点(`native_call_depth == 0`),无在途 builtin /// 局部裸指针、dispatch 未重入。 /// diff --git a/crates/oxide_vm/src/session_gc/tests.rs b/crates/oxide_vm/src/session_gc/tests.rs index 576870c5..8154479e 100644 --- a/crates/oxide_vm/src/session_gc/tests.rs +++ b/crates/oxide_vm/src/session_gc/tests.rs @@ -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::>()); + })); + 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"); diff --git a/crates/oxide_vm/src/suspended.rs b/crates/oxide_vm/src/suspended.rs index ccc6805b..f52b6c92 100644 --- a/crates/oxide_vm/src/suspended.rs +++ b/crates/oxide_vm/src/suspended.rs @@ -30,7 +30,7 @@ pub(crate) struct SuspendedFrame { pub save_stack: Vec, pub cell_stack: Vec>, pub try_stack: Vec, - pub for_in_iters: Vec<*mut ForInIter<'static>>, + pub for_in_iters: Vec<*mut ForInIter>, pub for_of_iters: Vec, /// `yield*` 委托中的内层迭代器;异步函数恒为 None。 pub delegated_iterator: Option, @@ -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::*; diff --git a/crates/oxide_vm/src/vm/frames.rs b/crates/oxide_vm/src/vm/frames.rs index 11258fd7..4521edec 100644 --- a/crates/oxide_vm/src/vm/frames.rs +++ b/crates/oxide_vm/src/vm/frames.rs @@ -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, } diff --git a/crates/oxide_vm/src/vm/gc_hooks.rs b/crates/oxide_vm/src/vm/gc_hooks.rs index d1232e69..786eca48 100644 --- a/crates/oxide_vm/src/vm/gc_hooks.rs +++ b/crates/oxide_vm/src/vm/gc_hooks.rs @@ -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 放弃执行期 /// 收集),正确性优先于收益; /// - 新增挂起态持有者须在此登记。 diff --git a/crates/oxide_vm/src/vm/inline.rs b/crates/oxide_vm/src/vm/inline.rs index 5c22c58a..c60d2dbb 100644 --- a/crates/oxide_vm/src/vm/inline.rs +++ b/crates/oxide_vm/src/vm/inline.rs @@ -32,7 +32,7 @@ pub(crate) struct InlineSyncState { pub(crate) pending_exception: Option, pub(crate) pending_error_kind: Option<&'static str>, pub(crate) pending_completion: Option, - pub(crate) for_in_iters: Vec<*mut ForInIter<'static>>, + pub(crate) for_in_iters: Vec<*mut ForInIter>, pub(crate) for_of_iters: Vec, pub(crate) saved_bytecode_stack: Vec>, 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)); + } + } + } +} diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 0b296f02..4452b94e 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -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::>()); + 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::>()); + // std Vec 直接作为迭代器体;体是堆上 Box,生命周期随迭代器表与 + // 挂起状态盒,出表/状态盒释放时逐条释放。 + let iter = Box::into_raw(Box::new(ForInIter { keys: keys_vec, index: 0 })); + self.iters.push_for_in(iter); Ok(()) } diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 89afa104..671aa8d0 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -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) { + pub(crate) fn restore_inline_state(&mut self, mut saved: Box) { 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(); diff --git a/crates/oxide_vm/src/vm_state.rs b/crates/oxide_vm/src/vm_state.rs index 1cf73320..1244f93d 100644 --- a/crates/oxide_vm/src/vm_state.rs +++ b/crates/oxide_vm/src/vm_state.rs @@ -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, } 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()) } diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index aa96f95e..1089269a 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -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(); diff --git a/crates/oxide_vm/src/vm_support/tests.rs b/crates/oxide_vm/src/vm_support/tests.rs index cc4b7df9..de0cc9b7 100644 --- a/crates/oxide_vm/src/vm_support/tests.rs +++ b/crates/oxide_vm/src/vm_support/tests.rs @@ -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::>()); + 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(),