mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
refactor: 收集路径原地化退役移动式sweep与门控
This commit is contained in:
@@ -697,20 +697,11 @@ fn async_state_mut(obj: &JsObject) -> Option<&mut AsyncState> {
|
|||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
// SAFETY: native_data 在 create_async_context 中由 Box::into_raw 分配,
|
// SAFETY: native_data 在 create_async_context 中由 Box::into_raw 分配,
|
||||||
// 生命周期与异步上下文对象一致;GC 路径(mark 边收集、执行期门控扫描)只读状态盒。
|
// 生命周期与异步上下文对象一致;GC 路径(mark 边收集)只读状态盒。
|
||||||
Some(unsafe { &mut *ptr })
|
Some(unsafe { &mut *ptr })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 异步挂起帧是否持有 for-in 迭代器:执行期收集门控按指针扫描
|
|
||||||
/// (迭代器体是堆上 Box,门控是保守门)。
|
|
||||||
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 边):对象/字符串/BigInt 均产出,
|
/// 异步状态内全部引用边的扁平列表(GC mark 边):对象/字符串/BigInt 均产出,
|
||||||
/// 消费侧按值类型分发到对象栈与存活集。
|
/// 消费侧按值类型分发到对象栈与存活集。
|
||||||
pub(crate) fn async_native_edges(obj: &JsObject) -> Vec<JsValue> {
|
pub(crate) fn async_native_edges(obj: &JsObject) -> Vec<JsValue> {
|
||||||
|
|||||||
@@ -1671,20 +1671,11 @@ fn async_gen_state_mut(obj: &JsObject) -> Option<&mut AsyncGeneratorState> {
|
|||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
// SAFETY: native_data 在 create_async_generator_object 中由 Box::into_raw 分配,
|
// SAFETY: native_data 在 create_async_generator_object 中由 Box::into_raw 分配,
|
||||||
// 生命周期与异步生成器对象一致;GC 路径(mark 边收集、执行期门控扫描)只读状态盒。
|
// 生命周期与异步生成器对象一致;GC 路径(mark 边收集)只读状态盒。
|
||||||
Some(unsafe { &mut *ptr })
|
Some(unsafe { &mut *ptr })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 异步生成器挂起帧是否持有 for-in 迭代器:执行期收集门控按指针扫描
|
|
||||||
/// (迭代器体是堆上 Box,门控是保守门)。
|
|
||||||
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 边):对象/字符串/BigInt
|
/// 异步生成器状态内全部引用边的扁平列表(GC mark 边):对象/字符串/BigInt
|
||||||
/// 均产出,消费侧按值类型分发到对象栈与存活集。
|
/// 均产出,消费侧按值类型分发到对象栈与存活集。
|
||||||
pub(crate) fn async_generator_native_edges(obj: &JsObject) -> Vec<JsValue> {
|
pub(crate) fn async_generator_native_edges(obj: &JsObject) -> Vec<JsValue> {
|
||||||
|
|||||||
@@ -959,20 +959,11 @@ fn generator_state_mut(obj: &JsObject) -> Option<&mut GeneratorState> {
|
|||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
// SAFETY: native_data 在 create_generator_object 中由 Box::into_raw 分配,
|
// SAFETY: native_data 在 create_generator_object 中由 Box::into_raw 分配,
|
||||||
// 生命周期与生成器对象一致;GC 路径(mark 边收集、执行期门控扫描)只读状态盒。
|
// 生命周期与生成器对象一致;GC 路径(mark 边收集)只读状态盒。
|
||||||
Some(unsafe { &mut *ptr })
|
Some(unsafe { &mut *ptr })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 生成器挂起帧是否持有 for-in 迭代器:执行期收集门控按指针扫描
|
|
||||||
/// (迭代器体是堆上 Box,门控是保守门)。
|
|
||||||
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 边):对象/字符串/BigInt 均产出,
|
/// 生成器状态内全部引用边的扁平列表(GC mark 边):对象/字符串/BigInt 均产出,
|
||||||
/// 消费侧按值类型分发到对象栈与存活集。
|
/// 消费侧按值类型分发到对象栈与存活集。
|
||||||
pub(crate) fn generator_native_edges(obj: &JsObject) -> Vec<JsValue> {
|
pub(crate) fn generator_native_edges(obj: &JsObject) -> Vec<JsValue> {
|
||||||
@@ -1031,7 +1022,7 @@ pub(crate) fn generator_native_cell_edges(obj: &JsObject) -> Vec<*mut Cell> {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 用转发函数重写状态快照中的所有 JsValue(session GC 移动式清扫 / promote 用)。
|
/// 用转发函数重写状态快照中的所有 JsValue(promote 用)。
|
||||||
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;
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ pub(crate) fn promise_native_edges(obj: &JsObject) -> Vec<JsValue> {
|
|||||||
edges
|
edges
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 用转发函数重写状态盒中的所有 JsValue(session GC 移动式清扫 / promote 用)。
|
/// 用转发函数重写状态盒中的所有 JsValue(promote 用)。
|
||||||
///
|
///
|
||||||
/// 结算链指针是裸指针边、不在本函数改写面:晋升路径克隆新分配于 session
|
/// 结算链指针是裸指针边、不在本函数改写面:晋升克隆新分配于 session arena
|
||||||
/// arena(原件地址不变),同 arena 改写场景指针天然有效;搬移换址场景由
|
/// (原件地址不变),改写场景指针天然有效;结算链接链由
|
||||||
/// 调用方在重写完成后经 `repoint_promise_promoted_clone` 按转发表重定位。
|
/// `migrate_settlement_to_newest_clone` 负责。
|
||||||
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;
|
||||||
@@ -53,12 +53,12 @@ pub(crate) fn rewrite_promise_native(obj: &JsObject, mut rewrite: impl FnMut(JsV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 深拷贝状态盒到新对象(promote / sweep 用):新对象持独立 Box,源盒可安全释放。
|
/// 深拷贝状态盒到新对象(promote 用):新对象持独立 Box,源盒可安全释放。
|
||||||
///
|
///
|
||||||
/// 源的反应**迁移**(非复制)进新对象:源盒随 epoch 释放,留在源上的反应会
|
/// 源的反应**迁移**(非复制)进新对象:源盒随 epoch 释放,留在源上的反应会
|
||||||
/// 永久丢失、原件侧消费者永不触发;每条反应的 JsValue 随状态盒其余字段同一
|
/// 永久丢失、原件侧消费者永不触发;每条反应的 JsValue 随状态盒其余字段同一
|
||||||
/// pass 晋升/改写。结算链指针按原值保留(晋升场景由
|
/// pass 晋升/改写。结算链指针按原值保留(接链由
|
||||||
/// `migrate_settlement_to_newest_clone` 接链,搬移场景按转发表重定位)。
|
/// `migrate_settlement_to_newest_clone` 负责)。
|
||||||
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,
|
||||||
) {
|
) {
|
||||||
@@ -144,16 +144,6 @@ pub(crate) fn migrate_settlement_to_newest_clone(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 按给定转发改写结算链指针(裸指针边、非 JsValue),session GC 搬移后调用。
|
|
||||||
pub(crate) fn repoint_promise_promoted_clone(obj: &JsObject, forward: impl FnOnce(*mut JsObject) -> *mut JsObject) {
|
|
||||||
let Some(state) = promise_state_mut(obj) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if !state.promoted_clone.is_null() {
|
|
||||||
state.promoted_clone = forward(state.promoted_clone);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 改写单条反应的四条 JsValue 引用(派生 promise 能力与处理器),源反应迁移
|
/// 改写单条反应的四条 JsValue 引用(派生 promise 能力与处理器),源反应迁移
|
||||||
/// 与再晋升合并共用。
|
/// 与再晋升合并共用。
|
||||||
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 {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ mod settlement;
|
|||||||
|
|
||||||
pub(crate) use gc_edges::{
|
pub(crate) use gc_edges::{
|
||||||
clone_promise_native_with_rewrite, drop_promise_native, migrate_settlement_to_newest_clone, promise_native_edges,
|
clone_promise_native_with_rewrite, drop_promise_native, migrate_settlement_to_newest_clone, promise_native_edges,
|
||||||
promise_native_size, repoint_promise_promoted_clone, rewrite_promise_native,
|
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;
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ use crate::vm::Vm;
|
|||||||
use oxide_builtins::{array_buffer, data_view, disposable_stack, map, module, regexp, set, typed_array, weak_map};
|
use oxide_builtins::{array_buffer, data_view, disposable_stack, map, module, regexp, set, typed_array, weak_map};
|
||||||
|
|
||||||
impl Vm {
|
impl Vm {
|
||||||
/// 单对象晋升测试入口:取/清共享转发表后调 `promote_object_inner`。
|
/// 单对象晋升测试入口:局部转发表下调 `promote_object_inner`。
|
||||||
/// 生产路径(边界修复/执行期晋升档/根晋升)各自持表管理,不经过此包装。
|
/// 生产路径(边界修复/根晋升)各自持表管理,不经过此包装。
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn promote_object(&mut self, src: *mut JsObject) -> *mut JsObject {
|
pub(crate) fn promote_object(&mut self, src: *mut JsObject) -> *mut JsObject {
|
||||||
vm_debug!("promote_object: src={:p}", src);
|
vm_debug!("promote_object: src={:p}", src);
|
||||||
let mut forwarding = std::mem::take(&mut self.gc_state.forwarding);
|
let mut forwarding = HashMap::with_hasher(FxBuildHasher);
|
||||||
let result = self.promote_object_inner(src, &mut forwarding);
|
let result = self.promote_object_inner(src, &mut forwarding);
|
||||||
|
// 局部表随作用域丢弃,晋升克隆的转发改写已在其上完成。
|
||||||
forwarding.clear();
|
forwarding.clear();
|
||||||
self.gc_state.forwarding = forwarding;
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ impl Vm {
|
|||||||
});
|
});
|
||||||
} else if src_ref.is_weak_map_obj() {
|
} else if src_ref.is_weak_map_obj() {
|
||||||
// 值边随克隆晋升(强边);弱键原样搬运,生死交晋升收敛后的
|
// 值边随克隆晋升(强边);弱键原样搬运,生死交晋升收敛后的
|
||||||
// 弱键定夺路径按同一转发表判定。
|
// 弱键定夺路径(现为原样保留)。
|
||||||
weak_map::clone_weak_map_native_with_rewrite(src_ref, dst_ref, |value| {
|
weak_map::clone_weak_map_native_with_rewrite(src_ref, dst_ref, |value| {
|
||||||
self.promote_value_if_epoch_object(value, forwarding)
|
self.promote_value_if_epoch_object(value, forwarding)
|
||||||
});
|
});
|
||||||
@@ -145,7 +145,8 @@ impl Vm {
|
|||||||
/// global 对象的属性值(逃逸写直通后 epoch 值直落 global 槽,global 不入
|
/// global 对象的属性值(逃逸写直通后 epoch 值直落 global 槽,global 不入
|
||||||
/// session 对象表,由 `rewrite_session_epoch_refs` 同趟改写)。
|
/// session 对象表,由 `rewrite_session_epoch_refs` 同趟改写)。
|
||||||
/// `rewrite_object_values` 覆盖元素/meta/属性/proto/captured_this/home_object/
|
/// `rewrite_object_values` 覆盖元素/meta/属性/proto/captured_this/home_object/
|
||||||
/// cell 值;克隆子树经 forwarding 去重,环与共享引用各克隆一次。
|
/// cell 值;克隆子树经局部转发表去重,环与共享引用各克隆一次。
|
||||||
|
/// 统一入口 Box 化后生产路径不产 epoch 对象,本方法为晋升族空转保留面。
|
||||||
///
|
///
|
||||||
/// # 注意事项
|
/// # 注意事项
|
||||||
/// - 须在执行外的安全点调用(无在途 builtin 局部裸指针、dispatch 未重入):
|
/// - 须在执行外的安全点调用(无在途 builtin 局部裸指针、dispatch 未重入):
|
||||||
@@ -154,14 +155,12 @@ impl Vm {
|
|||||||
/// - session 对象表为空时 global 的 epoch 子引用仍须修复,不做空表早退。
|
/// - session 对象表为空时 global 的 epoch 子引用仍须修复,不做空表早退。
|
||||||
pub fn promote_session_epoch_refs(&mut self) {
|
pub fn promote_session_epoch_refs(&mut self) {
|
||||||
let objects = std::mem::take(&mut self.gc_state.session_object_ptrs);
|
let objects = std::mem::take(&mut self.gc_state.session_object_ptrs);
|
||||||
let mut forwarding = std::mem::take(&mut self.gc_state.forwarding);
|
let mut forwarding = HashMap::with_hasher(FxBuildHasher);
|
||||||
self.rewrite_session_epoch_refs(&objects, &mut forwarding);
|
self.rewrite_session_epoch_refs(&objects, &mut forwarding);
|
||||||
// 弱键按收敛后的转发表定生死:未入表 epoch 键随 epoch 释放而亡。
|
// 弱键定夺退化为原样保留(定夺路径无克隆可重指,见
|
||||||
|
// resolve_weak_key_after_promotion);调用保留以收口族内流程。
|
||||||
self.rewrite_weak_map_keys_after_promotion(&objects, &mut forwarding);
|
self.rewrite_weak_map_keys_after_promotion(&objects, &mut forwarding);
|
||||||
forwarding.clear();
|
// 局部转发表随作用域丢弃:晋升新克隆已推入对象表,旧对象按原序拼回。
|
||||||
self.gc_state.forwarding = forwarding;
|
|
||||||
// 改写期新克隆已随晋升推入 gc_state 侧的表(此刻仅含克隆体),把取出的
|
|
||||||
// 旧对象按原相对序拼回同一表,表 = 克隆体 + 旧对象,各恰登记一次。
|
|
||||||
self.gc_state.session_object_ptrs.extend(objects);
|
self.gc_state.session_object_ptrs.extend(objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,8 +172,8 @@ impl Vm {
|
|||||||
/// 与列表同趟改写,无 native 盒只覆盖 JS 边。
|
/// 与列表同趟改写,无 native 盒只覆盖 JS 边。
|
||||||
///
|
///
|
||||||
/// # 注意事项
|
/// # 注意事项
|
||||||
/// - 调用方持有转发表期间不得让其它晋升路径改动 `gc_state.forwarding`;
|
/// - 调用方持有局部转发表期间,晋升新克隆推入的对象表由调用方决定何时
|
||||||
/// 晋升新克隆推入的对象表由调用方决定何时拼回。
|
/// 拼回;本方法不改表。
|
||||||
/// - 死对象(不可达)的 epoch 子引用改写会把死对象一并克隆进 session——
|
/// - 死对象(不可达)的 epoch 子引用改写会把死对象一并克隆进 session——
|
||||||
/// 克隆体无根,下一轮收集按死对象出表,不泄漏也不悬垂。
|
/// 克隆体无根,下一轮收集按死对象出表,不泄漏也不悬垂。
|
||||||
pub(crate) fn rewrite_session_epoch_refs(
|
pub(crate) fn rewrite_session_epoch_refs(
|
||||||
@@ -244,15 +243,15 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 晋升收敛后的弱键定夺:对候选集(session 对象列表 + 已晋升克隆)中的
|
/// 晋升收敛后的弱键定夺:对候选集(session 对象列表 + 已晋升克隆)中的
|
||||||
/// 每个弱表按转发表判定弱键——表内键改指新址,未入表 epoch 键为死键丢
|
/// 每个弱表按定夺路径处理条目。统一入口 Box 化后定夺退化为原样保留
|
||||||
/// 条目,session/P 键保留。
|
/// (无克隆可重指、reset 边界 mark 位无本轮可达性信息),本路径不改表项。
|
||||||
///
|
///
|
||||||
/// # 副作用
|
/// # 副作用
|
||||||
/// - 每个弱表原位重建条目表;无死键时条目与容量口径不变。
|
/// - 每个弱表原位重建条目表(整表重建口径,条目内容不变)。
|
||||||
///
|
///
|
||||||
/// # 边界与前提
|
/// # 边界与前提
|
||||||
/// - 须在主改写路径(JS 边 + 值边)对转发表收敛完成后调用;转发表届时
|
/// - 须在主改写路径(JS 边 + 值边)对转发表收敛完成后调用;转发表届时
|
||||||
/// 即最终强可达集。调用方负责其后的清表。
|
/// 即最终强可达集(统一入口树上恒空)。调用方负责其后的清表。
|
||||||
/// - global 上的弱表经其晋升克隆进入转发表值集,候选集无需单列 global。
|
/// - global 上的弱表经其晋升克隆进入转发表值集,候选集无需单列 global。
|
||||||
pub(crate) fn rewrite_weak_map_keys_after_promotion(
|
pub(crate) fn rewrite_weak_map_keys_after_promotion(
|
||||||
&mut self, objects: &[*mut JsObject], forwarding: &mut HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
|
&mut self, objects: &[*mut JsObject], forwarding: &mut HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
|
||||||
@@ -267,7 +266,7 @@ impl Vm {
|
|||||||
if obj.is_weak_map_obj() {
|
if obj.is_weak_map_obj() {
|
||||||
weak_map::rewrite_weak_map_native(
|
weak_map::rewrite_weak_map_native(
|
||||||
obj,
|
obj,
|
||||||
|key| crate::session_gc::resolve_weak_key_after_promotion(key, forwarding),
|
|key| crate::session_gc::resolve_weak_key_after_promotion(key),
|
||||||
|value| value,
|
|value| value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -280,7 +279,7 @@ impl Vm {
|
|||||||
if obj.is_weak_map_obj() {
|
if obj.is_weak_map_obj() {
|
||||||
weak_map::rewrite_weak_map_native(
|
weak_map::rewrite_weak_map_native(
|
||||||
obj,
|
obj,
|
||||||
|key| crate::session_gc::resolve_weak_key_after_promotion(key, forwarding),
|
|key| crate::session_gc::resolve_weak_key_after_promotion(key),
|
||||||
|value| value,
|
|value| value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -289,26 +288,19 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 把根直接持有的 epoch 对象(顶层 var 寄存器、挂起句柄等)晋升进 session,
|
/// 把根直接持有的 epoch 对象(顶层 var 寄存器、挂起句柄等)晋升进 session,
|
||||||
/// 并把根引用改写到克隆体。
|
/// 并把根引用改写到克隆体。统一入口 Box 化后生产路径无 epoch 对象,
|
||||||
|
/// 本方法为晋升族空转保留面(基准留存测量与图留存测试仍经此入口)。
|
||||||
///
|
///
|
||||||
/// # 副作用
|
/// # 副作用
|
||||||
/// - 每个根 epoch 对象深克隆进 session(含 native 状态盒,子引用递归晋升),
|
/// - 每个根 epoch 对象深克隆进 session(含 native 状态盒,子引用递归晋升),
|
||||||
/// 账目计入克隆字节;根引用按转发表重写。
|
/// 账目计入克隆字节;根引用按局部转发表重写。
|
||||||
///
|
///
|
||||||
/// # 注意事项
|
/// # 注意事项
|
||||||
/// - 须在执行外的安全点调用(无在途 builtin 局部裸指针、dispatch 未重入):
|
/// - 须在执行外的安全点调用(无在途 builtin 局部裸指针、dispatch 未重入):
|
||||||
/// 本方法改写全部根寄存器槽,执行期调用将使 builtin 局部裸指针失效
|
/// 本方法改写全部根寄存器槽,执行期调用将使 builtin 局部裸指针失效
|
||||||
/// (与 `collect_session_gc` 同前提)。
|
/// (与 `collect_session_gc` 同前提)。
|
||||||
/// - 供 workload 后观测点(基准留存测量)使用:本方法之后存活集完全对
|
|
||||||
/// session 可见,后续完整 GC 的留存账目不再漏"仅驻留 epoch arena 的对象"。
|
|
||||||
/// - 与 `promote_session_epoch_refs` 互补:本方法处理根直接持有的 epoch 对象,
|
/// - 与 `promote_session_epoch_refs` 互补:本方法处理根直接持有的 epoch 对象,
|
||||||
/// 后者处理 session 对象持有的 epoch 子引用(闭包捕获等绕过写屏障的来源)。
|
/// 后者处理 session 对象持有的 epoch 子引用(闭包捕获等绕过写屏障的来源)。
|
||||||
/// - 弱键定夺(`rewrite_weak_map_keys_after_promotion`)只按根直接子树的
|
|
||||||
/// 转发表为判据:经 session 对象(非根)强持的 epoch 键不在表内会被判死
|
|
||||||
/// 丢条目,而本调用不释放 epoch——键强存活时条目即消失。配对调用
|
|
||||||
/// (随后 `promote_session_epoch_refs`)由第二趟收敛转发表,但第一趟已丢
|
|
||||||
/// 条目不可复原;独立调用须根直接子树已覆盖全部强可达 epoch 对象
|
|
||||||
/// (即不存在经 session 对象强持的弱键)方为合法时机。
|
|
||||||
pub fn promote_rooted_epoch_objects(&mut self) {
|
pub fn promote_rooted_epoch_objects(&mut self) {
|
||||||
let mut epoch_roots = Vec::new();
|
let mut epoch_roots = Vec::new();
|
||||||
self.for_each_root(|value| {
|
self.for_each_root(|value| {
|
||||||
@@ -322,23 +314,22 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let mut forwarding = std::mem::take(&mut self.gc_state.forwarding);
|
// 局部转发表:晋升克隆去重共享与环,作用域结束即弃。
|
||||||
|
let mut forwarding = HashMap::with_hasher(FxBuildHasher);
|
||||||
for ptr in epoch_roots {
|
for ptr in epoch_roots {
|
||||||
self.promote_object_inner(ptr, &mut forwarding);
|
self.promote_object_inner(ptr, &mut forwarding);
|
||||||
}
|
}
|
||||||
crate::session_gc::rewrite_vm_roots(self, &forwarding);
|
crate::session_gc::rewrite_vm_roots(self, &forwarding);
|
||||||
// 弱表键定夺:根直接持有的弱表克隆其弱键按转发表判生死。
|
// 弱表键定夺退化为原样保留(定夺路径无克隆可重指,见
|
||||||
|
// resolve_weak_key_after_promotion);调用保留以收口族内流程。
|
||||||
self.rewrite_weak_map_keys_after_promotion(&[], &mut forwarding);
|
self.rewrite_weak_map_keys_after_promotion(&[], &mut forwarding);
|
||||||
forwarding.clear();
|
|
||||||
self.gc_state.forwarding = forwarding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 逃逸写屏障:写向全局/session 目标的对象值原样直通。
|
/// 逃逸写屏障:写向全局/session 目标的对象值原样直通。
|
||||||
///
|
///
|
||||||
/// session 对象持有 epoch 子引用是 GC 设计的一等存活态:epoch 边界 reset
|
/// session 对象持有 epoch 子引用是 GC 设计的一等存活态:epoch 边界 reset
|
||||||
/// 经 `promote_session_epoch_refs` 统一克隆晋升(转发表去重共享与环),
|
/// 经 `promote_session_epoch_refs` 统一克隆晋升(转发表去重共享与环)
|
||||||
/// 执行期收集晋升档按同一转发表改写根与存活 session 对象子引用,两条
|
/// 覆盖全部跨界持有面;builtin 调用期内 epoch 不重置,帧局部裸指针
|
||||||
/// 路径覆盖全部跨界持有面;builtin 调用期内 epoch 不重置,帧局部裸指针
|
|
||||||
/// 全程有效。此处若克隆,同一逻辑对象分裂为原件与克隆两份,经克隆的写
|
/// 全程有效。此处若克隆,同一逻辑对象分裂为原件与克隆两份,经克隆的写
|
||||||
/// 对方经原件的读不可见(builtin 帧指针与逃逸目标双引用并存时即暴露)。
|
/// 对方经原件的读不可见(builtin 帧指针与逃逸目标双引用并存时即暴露)。
|
||||||
pub(crate) fn promote_if_needed_for_write_ptr(&mut self, _target_ptr: *mut JsObject, value: JsValue) -> JsValue {
|
pub(crate) fn promote_if_needed_for_write_ptr(&mut self, _target_ptr: *mut JsObject, value: JsValue) -> JsValue {
|
||||||
@@ -530,20 +521,4 @@ mod tests {
|
|||||||
assert!(!is_epoch_object(&vm, meta.get));
|
assert!(!is_epoch_object(&vm, meta.get));
|
||||||
assert!(!is_epoch_object(&vm, meta.set));
|
assert!(!is_epoch_object(&vm, meta.set));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn promote_clears_forwarding_map() {
|
|
||||||
let mut vm = Vm::new();
|
|
||||||
let first = plain_object(&mut vm);
|
|
||||||
let promoted = vm.promote_object(first);
|
|
||||||
assert!(!promoted.is_null());
|
|
||||||
// 共享 forwarding 表必须在每次 promote 后清空,使后续 promote(或 GC 清扫)
|
|
||||||
// 永不观察到过期的 old->new 映射。
|
|
||||||
assert!(vm.gc_state.forwarding.is_empty());
|
|
||||||
|
|
||||||
let second = plain_object(&mut vm);
|
|
||||||
let promoted2 = vm.promote_object(second);
|
|
||||||
assert!(!promoted2.is_null());
|
|
||||||
assert!(vm.gc_state.forwarding.is_empty());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+160
-266
@@ -13,10 +13,10 @@ use oxide_builtins::{array_buffer, data_view, disposable_stack, map, module, reg
|
|||||||
|
|
||||||
/// session 级 mark-sweep GC 的状态与统计。
|
/// session 级 mark-sweep GC 的状态与统计。
|
||||||
///
|
///
|
||||||
/// 回收 session arena 中不再可达的对象与 `Vm::new_string` 分配的 session 字符串:
|
/// 回收 session 对象表中不再可达的对象与 `Vm::new_string` 分配的 session 字符串:
|
||||||
/// `mark` 从 VM roots 标记存活对象,`sweep` 将存活对象复制进新 arena(移动式)、
|
/// `mark` 从 VM roots 标记存活对象,`sweep` 原地清扫(存活对象带位保留、地址不变,
|
||||||
/// 更新所有引用,并释放死对象;`sweep_session_strings` 按存活标记回收字符串。
|
/// 死对象本体、堆区与独占 upvalue 列表原地释放出表);`sweep_session_strings`
|
||||||
/// 所有统计字段供外部观测 GC 行为。
|
/// 按存活标记回收字符串。所有统计字段供外部观测 GC 行为。
|
||||||
pub struct SessionGc {
|
pub struct SessionGc {
|
||||||
pub total_collections: u64,
|
pub total_collections: u64,
|
||||||
pub total_bytes_freed: u64,
|
pub total_bytes_freed: u64,
|
||||||
@@ -51,8 +51,10 @@ impl SessionGc {
|
|||||||
self.clear_session_marks(vm);
|
self.clear_session_marks(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 只清 session 对象表的 mark 位。移动式 sweep 末尾的旧表对象已全释放/克隆
|
/// 只清 session 对象表的 mark 位。原地 sweep 后死对象已出表、存活对象
|
||||||
/// (克隆未标记),清位须与表重写同步执行,独立成臂供调用点按需取用。
|
/// 的位在活分支清掉,正常收集路径无残留位;此臂处理 strings-only 路径
|
||||||
|
/// 与收集前历史残留——残留 true 会让下一次 `mark` 的 DFS 在已标对象处
|
||||||
|
/// 短路,漏扫其新增引用边。
|
||||||
pub(crate) fn clear_session_marks(&mut self, vm: &mut Vm) {
|
pub(crate) fn clear_session_marks(&mut self, vm: &mut Vm) {
|
||||||
for &ptr in &vm.gc_state.session_object_ptrs {
|
for &ptr in &vm.gc_state.session_object_ptrs {
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
@@ -368,12 +370,13 @@ impl SessionGc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 从 VM roots 标记存活的 session/epoch 对象、字符串与 BigInt,供 `sweep` 判定。
|
/// 从 VM roots 标记存活 session 对象、字符串与 BigInt,供 `sweep` 判定。
|
||||||
///
|
///
|
||||||
/// 对象 DFS 同时跟踪 session 与 epoch 两张表,两族 mark 位同字节,晋升收集按
|
/// 对象 DFS 跟踪 session 对象单表(统一入口 Box 化后无 epoch 表);perm
|
||||||
/// “标记 ∩ epoch 表”取活集;字符串边走 rope 闭包传播(Cons 子节点与扁平化产物),
|
/// 对象不置位(其位跨收集残留会令 DFS 短路漏扫),走独立已访集防环。
|
||||||
/// BigInt 边直接入存活集。roots 由 `Vm::for_each_root` 枚举,字段清单与
|
/// 字符串边走 rope 闭包传播(Cons 子节点与扁平化产物),BigInt 边直接入
|
||||||
/// `rewrite_values` 一一对应、须同步。只置位不搬移,调用前须先 `clear_all_marks` 清位。
|
/// 存活集。roots 由 `Vm::for_each_root` 枚举,字段清单与 `rewrite_values`
|
||||||
|
/// 一一对应、须同步。只置位不搬移,调用前须先 `clear_all_marks` 清位。
|
||||||
pub(crate) fn mark(&mut self, vm: &Vm) {
|
pub(crate) fn mark(&mut self, vm: &Vm) {
|
||||||
vm_debug!("[GC] mark phase: {} roots", vm.gc_state.session_object_ptrs.len());
|
vm_debug!("[GC] mark phase: {} roots", vm.gc_state.session_object_ptrs.len());
|
||||||
let mut seeds = Vec::new();
|
let mut seeds = Vec::new();
|
||||||
@@ -464,20 +467,20 @@ impl SessionGc {
|
|||||||
|
|
||||||
/// 释放对象本体之外的堆外属性数据(元素区、元素 meta、hash 属性区、属性 meta
|
/// 释放对象本体之外的堆外属性数据(元素区、元素 meta、hash 属性区、属性 meta
|
||||||
/// 与各族 native 状态盒),按 capacity 经 `Box::from_raw` 各恰好释放一次,返回
|
/// 与各族 native 状态盒),按 capacity 经 `Box::from_raw` 各恰好释放一次,返回
|
||||||
/// 字节数。`obj_ptr` 为空时返回 0;`require_session` 为 true 时断言对象属
|
/// 字节数。`obj_ptr` 为空时返回 0。不含 upvalue 列表与对象本体,由调用方处理。
|
||||||
/// session epoch,epoch 原件走 false 分支。不含 upvalue 列表与对象本体,由调用方处理。
|
///
|
||||||
pub(crate) fn drop_object_heap_data(obj_ptr: *mut JsObject, require_session: bool) -> u64 {
|
/// # 边界与前提
|
||||||
|
/// - 统一对象表内全部对象为 session 对象(统一入口 Box 化后生产路径无
|
||||||
|
/// epoch 原件),本函数只服务表内对象的释放路径,不做归属断言。
|
||||||
|
pub(crate) fn drop_object_heap_data(obj_ptr: *mut JsObject) -> u64 {
|
||||||
if obj_ptr.is_null() {
|
if obj_ptr.is_null() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// SAFETY: `obj_ptr` 在调用本辅助函数前已校验,指向 VM session arena 拥有的
|
// SAFETY: `obj_ptr` 在调用本辅助函数前已校验,指向 VM 拥有的 session 对象。
|
||||||
// session 对象。只重建 JsObject::ensure_hash_props/ensure_prop_meta 分配的 Box,
|
// 只重建 JsObject::ensure_hash_props/ensure_prop_meta 分配的 Box,
|
||||||
// 且只在这里释放一次。
|
// 且只在这里释放一次。
|
||||||
unsafe {
|
unsafe {
|
||||||
let obj = &mut *obj_ptr;
|
let obj = &mut *obj_ptr;
|
||||||
if require_session {
|
|
||||||
debug_assert!(obj.is_session_epoch());
|
|
||||||
}
|
|
||||||
let mut freed_bytes = 0u64;
|
let mut freed_bytes = 0u64;
|
||||||
|
|
||||||
let elems_ptr = obj.array_elements_raw() as *mut Vec<JsValue>;
|
let elems_ptr = obj.array_elements_raw() as *mut Vec<JsValue>;
|
||||||
@@ -529,19 +532,23 @@ impl SessionGc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop_session_object_heap_data(obj_ptr: *mut JsObject) -> u64 {
|
|
||||||
Self::drop_object_heap_data(obj_ptr, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 释放一个死 session 对象:对象本体 + 堆数据 + upvalue 列表,返回释放字节数。
|
/// 释放一个死 session 对象:对象本体 + 堆数据 + upvalue 列表,返回释放字节数。
|
||||||
///
|
///
|
||||||
/// # 注意事项
|
/// # 步骤
|
||||||
/// 仅 sweep 死分支到达此处:只有存活对象被克隆、克隆与原件共享同一 upvalues
|
/// 1. 释放堆数据(元素区 / 属性区 / meta 区 / native 状态盒)。
|
||||||
/// Box,死对象从不被克隆,其 upvalue 列表 Box 无其他持有者,在此恰好释放
|
/// 2. 释放 upvalue 列表 Box 并置空对象侧字段。
|
||||||
/// 一次;存活分支绝不释放(克隆仍引用同一 Box)。释放后置空:死对象已移出
|
/// 3. 释放对象本体:堆载体(HEAP_BIT)经 `Box::from_raw` 释放;
|
||||||
/// 对象表,收尾统一释放按表枚举不会再见,置空保证对象侧幂等、无陈旧指针。
|
/// Bump 载体(晋升族克隆,仅测试形态)随旧 Bump 换新归还,不在此释放。
|
||||||
fn drop_dead_session_object(obj_ptr: *mut JsObject) -> u64 {
|
///
|
||||||
let mut freed = Self::drop_session_object_heap_data(obj_ptr) + size_of::<JsObject>() as u64;
|
/// # 边界与前提
|
||||||
|
/// - `obj_ptr` 必须非空:空指针时第 1 步返回 0,但本体字节与位域读取
|
||||||
|
/// 对空指针无效,调用点(sweep 死分支 / 收尾逐对象路径)先行跳过空位。
|
||||||
|
/// - 仅 sweep 死分支与统一收尾到达此处:死对象从不被克隆,upvalue 列表
|
||||||
|
/// Box 无其他持有者,在此恰好释放一次;存活分支绝不释放(若有克隆仍
|
||||||
|
/// 引用同一 Box)。释放后死对象已移出对象表,收尾统一释放按表枚举
|
||||||
|
/// 不会再见,置空保证对象侧幂等、无陈旧指针。
|
||||||
|
pub(crate) fn drop_dead_session_object(obj_ptr: *mut JsObject) -> u64 {
|
||||||
|
let mut freed = Self::drop_object_heap_data(obj_ptr) + size_of::<JsObject>() as u64;
|
||||||
// SAFETY: obj_ptr 来自 session 对象表,sweep 期间仍指向合法对象;
|
// SAFETY: obj_ptr 来自 session 对象表,sweep 期间仍指向合法对象;
|
||||||
// upvalues Box 仅本对象持有(死对象不克隆),保证恰好释放一次。
|
// upvalues Box 仅本对象持有(死对象不克隆),保证恰好释放一次。
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -553,8 +560,8 @@ impl SessionGc {
|
|||||||
(*obj_ptr).upvalues = std::ptr::null_mut();
|
(*obj_ptr).upvalues = std::ptr::null_mut();
|
||||||
std::mem::drop(vec);
|
std::mem::drop(vec);
|
||||||
}
|
}
|
||||||
// 释放对象本体:堆载体(HEAP_BIT)经 Box::from_raw 释放,arena 载体
|
// 释放对象本体:堆载体(HEAP_BIT)经 Box::from_raw 释放,Bump
|
||||||
// (克隆)随旧 arena 归还,不在此释放。
|
// 载体(克隆)随旧 Bump 换新归还,不在此释放。
|
||||||
if (*obj_ptr).is_heap_alloc() {
|
if (*obj_ptr).is_heap_alloc() {
|
||||||
drop(Box::from_raw(obj_ptr));
|
drop(Box::from_raw(obj_ptr));
|
||||||
}
|
}
|
||||||
@@ -576,172 +583,116 @@ impl SessionGc {
|
|||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 移动式清扫 session 对象:存活对象复制进新 arena,死对象原地释放。
|
/// 原地清扫 session 对象:存活对象带位保留(清位并入活分支)、地址不变,
|
||||||
|
/// 死对象释放本体、堆区与独占 upvalue 列表并出表;弱表弱键在死对象释放前
|
||||||
|
/// 按 mark 位定生死(死键条目丢弃)。免转发表与根重写。
|
||||||
///
|
///
|
||||||
/// 按 GC mark 位分流:存活对象经 `clone_for_session_epoch` 复制(native 盒随族深拷)
|
/// # 步骤
|
||||||
/// 并登记 forwarding 旧址→新址,死对象释放本体、堆区与独占 upvalue;随后按转发表重写
|
/// 1. 弱键定夺:对每个存活弱表按 mark 位重建条目表(此刻全部键对象
|
||||||
/// 新对象的值边、native 边与 promise 结算链裸指针,转发查找使共享与环去重到同一克隆。
|
/// 仍分配,位域可读,见 `resolve_weak_key_sweep` 时序前提)。
|
||||||
/// 最后重写全部根引用、换表并以新 Bump 接管 session epoch,旧 arena 整体归还并清 mark 位。
|
/// 2. 分流:已标对象保留并清位(无残留位),未标对象原地释放出表。
|
||||||
|
/// 3. 表回写存活集,按存活重算对象口径账目,累计扫描/存活/死统计。
|
||||||
|
///
|
||||||
|
/// # 副作用
|
||||||
|
/// - `session_object_ptrs` 仅剩存活对象;`session_bytes_allocated` 重算为
|
||||||
|
/// 存活对象字节(串/BigInt/cell 分量由各清扫路径自行补回);
|
||||||
|
/// - `total_*` 与 `last_collection_*` 统计按对象口径更新(绝对赋值,
|
||||||
|
/// 串/BigInt/cell 清扫在其上累加)。
|
||||||
|
///
|
||||||
|
/// # 边界与前提
|
||||||
|
/// - 调用前须完成 `mark`(位域为本次收集的判定依据);
|
||||||
|
/// - 死对象从不被克隆,upvalue 列表 Box 无其他持有者,恰好一次释放成立。
|
||||||
pub(crate) fn sweep(&mut self, vm: &mut Vm) -> u64 {
|
pub(crate) fn sweep(&mut self, vm: &mut Vm) -> u64 {
|
||||||
let old_ptrs = std::mem::take(&mut vm.gc_state.session_object_ptrs);
|
self.sweep_in_place(vm).2
|
||||||
let mut forwarding = std::mem::take(&mut vm.gc_state.forwarding);
|
}
|
||||||
let new_arena = bumpalo::Bump::new();
|
|
||||||
let mut survivors = 0u64;
|
|
||||||
let mut dead = 0u64;
|
|
||||||
let mut freed_bytes = 0u64;
|
|
||||||
|
|
||||||
for old_ptr in old_ptrs {
|
/// 原地 sweep 核心:弱键定夺、死对象释放与出表、存活重算、统计累计。
|
||||||
if old_ptr.is_null() {
|
/// `sweep`(完整收集)与 `collect_in_run`(执行期收集)共用,保证两条
|
||||||
|
/// 收集路径的弱键定夺口径一致。返回 (存活数, 死数, 释放字节)。
|
||||||
|
fn sweep_in_place(&mut self, vm: &mut Vm) -> (u64, u64, u64) {
|
||||||
|
let old_ptrs = std::mem::take(&mut vm.gc_state.session_object_ptrs);
|
||||||
|
let mut survivors = Vec::with_capacity(old_ptrs.len());
|
||||||
|
|
||||||
|
// 弱键定夺先于死对象释放:键对象(含死键)此刻全部仍分配,
|
||||||
|
// 对象头位域读取安全(时序前提见 resolve_weak_key_sweep)。
|
||||||
|
for &ptr in &old_ptrs {
|
||||||
|
if ptr.is_null() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// SAFETY: old_ptr 来自 session 对象表,sweep 运行期间仍指向合法对象。
|
// SAFETY: ptr 来自 session 对象表登记,sweep 运行期间有效。
|
||||||
let is_live = unsafe { (*old_ptr).is_gc_marked() };
|
let obj = unsafe { &*ptr };
|
||||||
let is_heap = unsafe { (*old_ptr).is_heap_alloc() };
|
if obj.is_gc_marked() && obj.is_weak_map_obj() {
|
||||||
if is_live {
|
// SAFETY: 弱表 native 盒独占,整表重建在定夺期间无并发读者。
|
||||||
survivors += 1;
|
unsafe {
|
||||||
let old_ref = unsafe { &*old_ptr };
|
weak_map::rewrite_weak_map_native(
|
||||||
let clone = old_ref.clone_for_session_epoch();
|
&mut *ptr,
|
||||||
let new_ptr = new_arena.alloc(clone) as *mut JsObject;
|
|key| resolve_weak_key_sweep(key),
|
||||||
let new_ref = unsafe { &mut *new_ptr };
|
|value| value,
|
||||||
if old_ref.is_map() {
|
);
|
||||||
map::clone_map_native_with_rewrite(old_ref, new_ref, |value| value);
|
|
||||||
} else if old_ref.is_set() {
|
|
||||||
set::clone_set_native_with_rewrite(old_ref, new_ref, |value| value);
|
|
||||||
} else if old_ref.is_weak_map_obj() {
|
|
||||||
// 弱键原样搬运:键生死按转发表收敛后的判定(phase 2 与
|
|
||||||
// 晋升定夺路径同判据),此处只深拷贝盒与改写值边。
|
|
||||||
weak_map::clone_weak_map_native_with_rewrite(old_ref, new_ref, |value| value);
|
|
||||||
} else if old_ref.is_disposable_stack_obj() || old_ref.is_async_disposable_stack_obj() {
|
|
||||||
disposable_stack::clone_dispose_native_with_rewrite(old_ref, new_ref, |value| value);
|
|
||||||
} else if old_ref.is_array_buffer_obj() {
|
|
||||||
array_buffer::clone_array_buffer_native(old_ref, new_ref);
|
|
||||||
} else if old_ref.is_shared_array_buffer_obj() {
|
|
||||||
array_buffer::clone_shared_array_buffer_native(old_ref, new_ref);
|
|
||||||
} else if old_ref.is_typed_array_obj() {
|
|
||||||
typed_array::clone_typed_array_native_with_rewrite(old_ref, new_ref, |value| value);
|
|
||||||
} else if old_ref.is_data_view_obj() {
|
|
||||||
data_view::clone_data_view_native_with_rewrite(old_ref, new_ref, |value| value);
|
|
||||||
} else if old_ref.is_promise_obj() {
|
|
||||||
crate::promise::clone_promise_native_with_rewrite(old_ref, new_ref, |value| value);
|
|
||||||
} else if old_ref.is_async_obj() {
|
|
||||||
crate::async_func::clone_async_native_with_rewrite(old_ref, new_ref, |value| value);
|
|
||||||
} else if old_ref.is_async_generator_obj() {
|
|
||||||
crate::async_generator::clone_async_generator_native_with_rewrite(old_ref, new_ref, |value| value);
|
|
||||||
} else if old_ref.is_generator_obj() {
|
|
||||||
crate::generator::clone_generator_native_with_rewrite(old_ref, new_ref, |value| value);
|
|
||||||
} else if old_ref.holds_compiled_regex() {
|
|
||||||
// 已编译正则是 Box 深拷贝到新对象:源 Box 由 drop 释放,互不共享。
|
|
||||||
regexp::clone_regexp_native(old_ref, new_ref);
|
|
||||||
} else if old_ref.is_module_namespace() {
|
|
||||||
// 条目表深拷贝:`clone_for_session_epoch` 只浅拷贝 native_data 指针。
|
|
||||||
module::clone_module_ns_native_with_rewrite(old_ref, new_ref, |value| value);
|
|
||||||
}
|
|
||||||
forwarding.insert(old_ptr, new_ptr);
|
|
||||||
freed_bytes += Self::drop_session_object_heap_data(old_ptr);
|
|
||||||
// 释放原件本体:堆载体(HEAP_BIT)经 Box::from_raw 释放,arena
|
|
||||||
// 载体(克隆)随旧 arena 归还,不在此释放。
|
|
||||||
if is_heap {
|
|
||||||
freed_bytes += size_of::<JsObject>() as u64;
|
|
||||||
unsafe {
|
|
||||||
drop(Box::from_raw(old_ptr));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按 mark 位分流:存活保留清位,死对象释放本体、堆区与 upvalue 出表。
|
||||||
|
let mut dead = 0u64;
|
||||||
|
let mut freed_bytes = 0u64;
|
||||||
|
for &ptr in &old_ptrs {
|
||||||
|
if ptr.is_null() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// SAFETY: ptr 来自 session 对象表登记,sweep 运行期间有效。
|
||||||
|
if unsafe { (*ptr).is_gc_marked() } {
|
||||||
|
// 原地 sweep 不搬移对象:存活对象带位保留,清位并入活分支
|
||||||
|
// (收集后无残留,残留 true 使下一次 mark DFS 短路漏标)。
|
||||||
|
unsafe { (*ptr).set_gc_mark(false) };
|
||||||
|
survivors.push(ptr);
|
||||||
} else {
|
} else {
|
||||||
|
// SAFETY: 死对象从不被克隆,upvalue 列表 Box 无其他持有者。
|
||||||
|
freed_bytes += Self::drop_dead_session_object(ptr);
|
||||||
dead += 1;
|
dead += 1;
|
||||||
freed_bytes += Self::drop_dead_session_object(old_ptr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let live = survivors.len() as u64;
|
||||||
|
vm.gc_state.session_object_ptrs = survivors;
|
||||||
|
|
||||||
for &dst in forwarding.values() {
|
// 对象口径重算:存活对象头 + 堆数据求和(串/BigInt/cell 分量不在此口径,
|
||||||
// SAFETY: forwarding 中的指针全是新分配且已初始化的对象。
|
// 由各清扫路径按存活补回,与完整收集的最终账目一致)。
|
||||||
let obj = unsafe { &mut *dst };
|
|
||||||
obj.rewrite_object_values(|value| {
|
|
||||||
if value.is_object() {
|
|
||||||
let ptr = value.as_js_object_ptr();
|
|
||||||
if let Some(&fwd) = forwarding.get(&ptr) {
|
|
||||||
return JsValue::from_js_object(fwd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value
|
|
||||||
});
|
|
||||||
if obj.is_map() {
|
|
||||||
map::rewrite_map_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
|
||||||
} else if obj.is_set() {
|
|
||||||
set::rewrite_set_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
|
||||||
} else if obj.is_weak_map_obj() {
|
|
||||||
// 键按转发表 + mark 位定生死(死键条目丢弃),值走强边转发改写。
|
|
||||||
weak_map::rewrite_weak_map_native(
|
|
||||||
obj,
|
|
||||||
|key| resolve_weak_key_sweep(key, &forwarding),
|
|
||||||
|value| rewrite_forwarded_value(value, &forwarding),
|
|
||||||
);
|
|
||||||
} else if obj.is_disposable_stack_obj() || obj.is_async_disposable_stack_obj() {
|
|
||||||
disposable_stack::rewrite_dispose_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
|
||||||
} else if obj.is_typed_array_obj() {
|
|
||||||
typed_array::rewrite_typed_array_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
|
||||||
} else if obj.is_data_view_obj() {
|
|
||||||
data_view::rewrite_data_view_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
|
||||||
} else if obj.is_generator_obj() {
|
|
||||||
crate::generator::rewrite_generator_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
|
||||||
} else if obj.is_promise_obj() {
|
|
||||||
crate::promise::rewrite_promise_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
|
||||||
// 结算链指针是裸指针边(非 JsValue):搬移换址后按转发表重定位,
|
|
||||||
// 旧克隆的结算传导仍指向后继克隆的新址。
|
|
||||||
crate::promise::repoint_promise_promoted_clone(obj, |ptr| {
|
|
||||||
rewrite_forwarded_value(JsValue::from_js_object(ptr), &forwarding).as_js_object_ptr()
|
|
||||||
});
|
|
||||||
} else if obj.is_async_obj() {
|
|
||||||
crate::async_func::rewrite_async_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
|
||||||
} else if obj.is_async_generator_obj() {
|
|
||||||
crate::async_generator::rewrite_async_generator_native(obj, |value| {
|
|
||||||
rewrite_forwarded_value(value, &forwarding)
|
|
||||||
});
|
|
||||||
} else if obj.is_module_namespace() {
|
|
||||||
module::rewrite_module_ns_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rewrite_vm_roots(vm, &forwarding);
|
|
||||||
|
|
||||||
vm.gc_state.session_object_ptrs = forwarding.values().copied().collect();
|
|
||||||
forwarding.clear();
|
|
||||||
vm.gc_state.forwarding = forwarding;
|
|
||||||
vm.gc_state.session_epoch = new_arena;
|
|
||||||
vm.gc_state.session_bytes_allocated = vm
|
vm.gc_state.session_bytes_allocated = vm
|
||||||
.gc_state
|
.gc_state
|
||||||
.session_object_ptrs
|
.session_object_ptrs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&&ptr| !ptr.is_null())
|
.filter(|&&ptr| !ptr.is_null())
|
||||||
.map(|&ptr| {
|
.map(|&ptr| {
|
||||||
|
// SAFETY: 存活对象在 session 表中,sweep 运行期间有效。
|
||||||
let obj = unsafe { &*ptr };
|
let obj = unsafe { &*ptr };
|
||||||
size_of::<JsObject>() as u64 + Self::object_heap_data_bytes(obj)
|
size_of::<JsObject>() as u64 + Self::object_heap_data_bytes(obj)
|
||||||
})
|
})
|
||||||
.sum::<u64>() as usize;
|
.sum::<u64>() as usize;
|
||||||
|
|
||||||
let total_ptrs = survivors + dead;
|
// 统计按对象口径累计(串/BigInt/cell 清扫在其上累加释放字节)。
|
||||||
if total_ptrs > 0 {
|
self.total_bytes_freed = self.total_bytes_freed.saturating_add(freed_bytes);
|
||||||
|
self.total_objects_scanned += live + dead;
|
||||||
|
self.total_objects_live += live;
|
||||||
|
self.total_objects_dead += dead;
|
||||||
|
self.last_collection_objects_scanned = live + dead;
|
||||||
|
self.last_collection_objects_live = live;
|
||||||
|
self.last_collection_objects_dead = dead;
|
||||||
|
self.last_collection_bytes_freed = freed_bytes;
|
||||||
|
|
||||||
|
if live + dead > 0 {
|
||||||
if dead == 0 {
|
if dead == 0 {
|
||||||
vm_debug!("[GC] sweep phase -> no objects collected ({} live, {} dead)", survivors, dead);
|
vm_debug!("[GC] sweep phase -> no objects collected ({} live, {} dead)", live, dead);
|
||||||
} else {
|
} else {
|
||||||
vm_debug!(
|
vm_debug!(
|
||||||
"[GC] sweep phase: {} scanned, {} live, {} dead, {} bytes",
|
"[GC] sweep phase: {} scanned, {} live, {} dead, {} bytes",
|
||||||
total_ptrs,
|
live + dead,
|
||||||
survivors,
|
live,
|
||||||
dead,
|
dead,
|
||||||
freed_bytes
|
freed_bytes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(live, dead, freed_bytes)
|
||||||
self.total_bytes_freed += freed_bytes;
|
|
||||||
self.total_objects_scanned += total_ptrs;
|
|
||||||
self.total_objects_live += survivors;
|
|
||||||
self.total_objects_dead += dead;
|
|
||||||
self.last_collection_objects_scanned = total_ptrs;
|
|
||||||
self.last_collection_objects_live = survivors;
|
|
||||||
self.last_collection_objects_dead = dead;
|
|
||||||
self.last_collection_bytes_freed = freed_bytes;
|
|
||||||
freed_bytes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 清扫 session `JsString`:保留 `mark()` 阶段记为存活的部分,其余经
|
/// 清扫 session `JsString`:保留 `mark()` 阶段记为存活的部分,其余经
|
||||||
@@ -884,10 +835,11 @@ impl SessionGc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 完整收集编排:`mark` → `sweep`(对象移动式搬移)→ `sweep_session_strings` →
|
/// 完整收集编排:`mark` → `sweep`(对象原地清扫)→ `sweep_session_strings` →
|
||||||
/// `sweep_session_bigints`,累加三类释放字节;字符串与 BigInt 地址稳定、无需
|
/// `sweep_session_bigints` → `sweep_session_cells`,累加三类释放字节;
|
||||||
/// forwarding。更新各表、字节账目与 mark 位,累计收集次数与最近/最大/最小耗时,
|
/// 对象与字符串、BigInt 地址均稳定、无需 forwarding。更新各表、字节账目与
|
||||||
/// 释放字节大于 0 或每满 100 次时输出统计摘要。
|
/// mark 位,累计收集次数与最近/最大/最小耗时,释放字节大于 0 或每满 100 次
|
||||||
|
/// 时输出统计摘要。
|
||||||
pub(crate) fn collect(&mut self, vm: &mut Vm) {
|
pub(crate) fn collect(&mut self, vm: &mut Vm) {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
@@ -922,9 +874,9 @@ impl SessionGc {
|
|||||||
|
|
||||||
/// 仅回收 session 字符串:完整 mark(对象只置位不搬移)→ 按存活集清扫字符串。
|
/// 仅回收 session 字符串:完整 mark(对象只置位不搬移)→ 按存活集清扫字符串。
|
||||||
///
|
///
|
||||||
/// 刻意跳过对象 sweep 搬移——builtin/dispatch 层持有跨分配点的 session 对象
|
/// 刻意跳过对象清扫——轻量档只走串表释放,对象口径的回收归完整收集与
|
||||||
/// 裸指针,移动式 sweep 会使其悬垂;对象执行期回收仍只在 reset 统一进行。
|
/// 执行期收集(dispatch 安全点)。字符串每串独立 Box、地址稳定,清扫
|
||||||
/// 字符串每串独立 Box、地址稳定,清扫无需 forwarding 与根重写。
|
/// 无需 forwarding 与根重写。
|
||||||
///
|
///
|
||||||
/// # 副作用
|
/// # 副作用
|
||||||
/// - 清空全部对象 mark 位;按存活集释放死串;累计 `total_collections` 与时长统计;
|
/// - 清空全部对象 mark 位;按存活集释放死串;累计 `total_collections` 与时长统计;
|
||||||
@@ -1006,22 +958,22 @@ impl SessionGc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 执行期收集:session 原地非移动 sweep。
|
/// 执行期收集:session 原地 sweep(与完整收集共用清扫核心)。
|
||||||
///
|
///
|
||||||
/// 在 dispatch 安全点(循环顶 + `native_call_depth == 0`)回收单 run 分配
|
/// 在 dispatch 安全点(循环顶 + `native_call_depth == 0`)回收单 run 分配
|
||||||
/// 包络内的死对象:死 session 对象原地释放(堆区 + upvalue 列表 + 本体)
|
/// 包络内的死对象:死 session 对象原地释放(堆区 + upvalue 列表 + 本体)
|
||||||
/// 并出表;存活对象不移动——地址不变、免 forwarding/rewrite,用户可观察
|
/// 并出表,弱键按 mark 位定夺;存活对象不移动——地址不变、免转发表与
|
||||||
/// identity 不分裂。
|
/// 根重写,用户可观察 identity 不分裂。
|
||||||
///
|
///
|
||||||
/// # 边界与前提
|
/// # 边界与前提
|
||||||
/// - 调用方已完成门控(无活跃/挂起 for-in):门控是保守门,迭代器体
|
/// - 无门控:for-in 迭代器体是堆上 Box,活跃形经 VM 表、挂起形经状态盒
|
||||||
/// 是堆上 Box,收集不搬移迭代器体;
|
/// 边进入根收集,键引用被 mark 标活,原地清扫不误释放;
|
||||||
/// - 调用点为 dispatch 安全点(`native_call_depth == 0`),无在途 builtin
|
/// - 调用点为 dispatch 安全点(`native_call_depth == 0`),无在途 builtin
|
||||||
/// 局部裸指针、dispatch 未重入。
|
/// 局部裸指针、dispatch 未重入。
|
||||||
///
|
///
|
||||||
/// # 副作用
|
/// # 副作用
|
||||||
/// - `session_object_ptrs` 仅剩存活,`session_bytes_allocated` 按存活重算
|
/// - `session_object_ptrs` 仅剩存活,`session_bytes_allocated` 按存活整体
|
||||||
/// (串/BigInt 归 strings-only 路径,本路径不动,仅对象口径变化);
|
/// 重算(对象 + 串 + BigInt;本路径不扫串/BigInt,串 GC 归 strings-only);
|
||||||
/// - `gc_watermark` = 当前分配包络 + 阈值增量;
|
/// - `gc_watermark` = 当前分配包络 + 阈值增量;
|
||||||
/// - 累计/时长统计更新;mark 位全清(收集后无残留)。
|
/// - 累计/时长统计更新;mark 位全清(收集后无残留)。
|
||||||
pub(crate) fn collect_in_run(&mut self, vm: &mut Vm) {
|
pub(crate) fn collect_in_run(&mut self, vm: &mut Vm) {
|
||||||
@@ -1033,44 +985,16 @@ impl SessionGc {
|
|||||||
|
|
||||||
self.mark(vm);
|
self.mark(vm);
|
||||||
|
|
||||||
// 死 session 对象原地释放(堆区 + upvalue 列表 + 本体)并出表;存活
|
// 死 session 对象原地释放(堆区 + upvalue 列表 + 本体)并出表,弱键
|
||||||
// 对象不动——地址不变,免 forwarding/rewrite。
|
// 按 mark 位定夺;存活对象不动——地址不变,免转发表与根重写。对象
|
||||||
let session_ptrs = std::mem::take(&mut vm.gc_state.session_object_ptrs);
|
// 口径统计由清扫核心累计,此处只补集合与账目。
|
||||||
let mut survivors = Vec::with_capacity(session_ptrs.len());
|
self.sweep_in_place(vm);
|
||||||
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() } {
|
|
||||||
// 原地 sweep 不搬移对象:存活对象带位保留,清位并入活分支
|
|
||||||
// (收集后无残留,残留 true 使下一次 mark DFS 短路漏标)。
|
|
||||||
unsafe { (*ptr).set_gc_mark(false) };
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 死 cell 随死对象出表:存活 cell 经 mark 种子 + 对象边入存活集,此处清扫。
|
// 死 cell 随死对象出表:存活 cell 经 mark 种子 + 对象边入存活集,此处清扫。
|
||||||
self.sweep_session_cells(vm);
|
self.sweep_session_cells(vm);
|
||||||
|
|
||||||
// 对象口径重算:存活对象 + 存活串 + BigInt(串/BigInt 本路径不动,
|
// 整体口径重算:对象分量已由清扫核心写入,此处补串与 BigInt 分量
|
||||||
// 随公式整体重算保持与 strings-only 口径一致)。
|
// (本路径不扫串/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;
|
let mut string_bytes: usize = 0;
|
||||||
for &ptr in &vm.gc_state.session_string_ptrs {
|
for &ptr in &vm.gc_state.session_string_ptrs {
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
@@ -1080,30 +1004,18 @@ impl SessionGc {
|
|||||||
string_bytes += size_of::<JsString>() + unsafe { (*ptr).payload_bytes() };
|
string_bytes += size_of::<JsString>() + unsafe { (*ptr).payload_bytes() };
|
||||||
}
|
}
|
||||||
let bigint_bytes = vm.gc_state.session_bigint_ptrs.borrow().len() * size_of::<num_bigint::BigInt>();
|
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.session_bytes_allocated =
|
||||||
|
vm.gc_state.session_bytes_allocated.saturating_add(string_bytes).saturating_add(bigint_bytes);
|
||||||
|
|
||||||
// 抬高下次触发水位:当前分配包络 + 阈值增量——存活包络超阈值时不每指令
|
// 抬高下次触发水位:当前分配包络 + 阈值增量——存活包络超阈值时不每指令
|
||||||
// 重复触发无死对象可回收的白跑。
|
// 重复触发无死对象可回收的白跑。
|
||||||
vm.gc_state.gc_watermark = vm.run_alloc_bytes().saturating_add(vm.gc_state.gc_threshold_cached);
|
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_session;
|
|
||||||
let dead = dead_session;
|
|
||||||
let freed_bytes = session_freed;
|
|
||||||
|
|
||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
self.total_collections += 1;
|
self.total_collections += 1;
|
||||||
self.last_collection_duration_us = elapsed.as_micros() as u64;
|
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.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.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 += session_ptrs.len() as u64;
|
|
||||||
self.total_objects_live += live;
|
|
||||||
self.total_objects_dead += dead;
|
|
||||||
self.last_collection_objects_scanned = 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!(
|
vm_info!(
|
||||||
"[GC] in-run cycle #{} end: {} scanned, {} live, {} dead, {:.1}ms, {} bytes freed",
|
"[GC] in-run cycle #{} end: {} scanned, {} live, {} dead, {:.1}ms, {} bytes freed",
|
||||||
@@ -1112,7 +1024,7 @@ impl SessionGc {
|
|||||||
self.last_collection_objects_live,
|
self.last_collection_objects_live,
|
||||||
self.last_collection_objects_dead,
|
self.last_collection_objects_dead,
|
||||||
elapsed.as_secs_f64() * 1000.0,
|
elapsed.as_secs_f64() * 1000.0,
|
||||||
freed_bytes,
|
self.last_collection_bytes_freed,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1165,56 +1077,38 @@ fn rewrite_forwarded_value(
|
|||||||
.unwrap_or(value)
|
.unwrap_or(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 移动式 sweep 改写期的弱键判定:转发表内键改指新址;未入表的 session/
|
/// 原地 sweep 的弱键判定:session 键按 mark 位定生死(已标 = 强可达保留,
|
||||||
/// epoch 键按 mark 位定生死(已标 = 强可达、随后晋升或原地存活,未标 =
|
/// 未标 = 死键丢弃),非 session 键(perm 对象等)不可死、恒保留;symbol
|
||||||
/// 死键丢弃);P 键不可死,恒保留。
|
/// 键按值恒等、恒保留。
|
||||||
///
|
///
|
||||||
/// # 边界与前提
|
/// # 边界与前提
|
||||||
/// - 未入表键读归属位与 mark 位须解引用旧指针:调用点(sweep 改写相)
|
/// - 时序前提:定夺须先于死对象本体释放执行——判定要读键对象本体的 mark
|
||||||
/// 旧 arena 尚未归还、清位发生在改写相之后,对象头位域可读。
|
/// 位域,调用点(sweep 弱键定夺相)位于死对象释放相之前,全部键对象(含
|
||||||
/// - 死键指针只作哈希键与位域读取,不 deref 其已释放的堆数据。
|
/// 死键)此刻仍分配,位域读取安全;死键指针此后只作被丢弃的表项内容,
|
||||||
pub(crate) fn resolve_weak_key_sweep(
|
/// 不再被解引用。
|
||||||
key: weak_map::WeakKey, forwarding: &HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
|
/// - 死键指针只读对象头位域,不解引用其堆数据(属性区等随释放销毁)。
|
||||||
) -> Option<weak_map::WeakKey> {
|
pub(crate) fn resolve_weak_key_sweep(key: weak_map::WeakKey) -> Option<weak_map::WeakKey> {
|
||||||
let weak_map::WeakKey::Obj(ptr) = key else {
|
let weak_map::WeakKey::Obj(ptr) = key else {
|
||||||
return Some(key);
|
return Some(key);
|
||||||
};
|
};
|
||||||
let mut_ptr = ptr as *mut JsObject;
|
let mut_ptr = ptr as *mut JsObject;
|
||||||
if let Some(&new) = forwarding.get(&mut_ptr) {
|
// SAFETY: 见时序前提——定夺相键对象仍分配,对象头位域可读。
|
||||||
return Some(weak_map::WeakKey::Obj(new as *const JsObject));
|
|
||||||
}
|
|
||||||
// SAFETY: 见函数边界与前提——sweep 改写相旧 arena 存活,对象头位域可读。
|
|
||||||
let old = unsafe { &*mut_ptr };
|
let old = unsafe { &*mut_ptr };
|
||||||
if (old.is_session_epoch() || old.is_epoch()) && !old.is_gc_marked() {
|
if old.is_session_epoch() && !old.is_gc_marked() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(key)
|
Some(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 晋升收敛后的弱键定夺:转发表内键改指新址;未入表 epoch 键 = 本轮晋升
|
/// 晋升收敛后的弱键定夺:统一入口 Box 化后晋升族在生产路径不产克隆
|
||||||
/// 未覆盖 = 死键丢弃;session 键不搬移、原样保留(惰性判定交下一轮 sweep);
|
/// (epoch 守卫恒假、无 epoch 对象),转发表恒空、无重指需求;reset 边界
|
||||||
/// P 键不可死,恒保留。
|
/// 的 mark 位已被前序收集清位,不携带本轮可达性信息。故定夺退化为原样
|
||||||
|
/// 保留——弱键生死由原地 sweep 的 mark 位判定单一裁定,本路径不改表项。
|
||||||
///
|
///
|
||||||
/// # 边界与前提
|
/// # 边界与前提
|
||||||
/// - 晋升定夺路径(原地晋升 / in-run 晋升档)的转发表已收敛为最终强可达集,
|
/// - 不解引用任何对象本体(无位域读取),无定夺时序面;
|
||||||
/// 判据不含 mark 位(reset 边界路径此前已清位)。
|
/// - 晋升族整体为死代码保留面,弱表族本体退役时一并删除。
|
||||||
/// - 解引用仅限未入表键的归属位读取,调用点旧 arena 存活(epoch 释放 /
|
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, forwarding: &HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
|
|
||||||
) -> Option<weak_map::WeakKey> {
|
|
||||||
let weak_map::WeakKey::Obj(ptr) = key else {
|
|
||||||
return Some(key);
|
|
||||||
};
|
|
||||||
let mut_ptr = ptr as *mut JsObject;
|
|
||||||
if let Some(&new) = forwarding.get(&mut_ptr) {
|
|
||||||
return Some(weak_map::WeakKey::Obj(new as *const JsObject));
|
|
||||||
}
|
|
||||||
// SAFETY: 见函数边界与前提——定夺点旧 arena 存活,对象头位域可读。
|
|
||||||
let old = unsafe { &*mut_ptr };
|
|
||||||
if old.is_epoch() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(key)
|
Some(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -224,7 +224,8 @@ fn sweep_preserves_cycle_and_collects_unreachable() {
|
|||||||
vm.gc_state.session_gc = gc;
|
vm.gc_state.session_gc = gc;
|
||||||
|
|
||||||
assert_eq!(vm.gc_state.session_object_ptrs.len(), 2);
|
assert_eq!(vm.gc_state.session_object_ptrs.len(), 2);
|
||||||
assert!(!vm.gc_state.session_object_ptrs.contains(&root_session));
|
// 原地 sweep 不搬移存活对象:根与子保持原址(克隆体即原对象),死对象出表。
|
||||||
|
assert!(vm.gc_state.session_object_ptrs.contains(&root_session));
|
||||||
assert!(!vm.gc_state.session_object_ptrs.contains(&dead_session));
|
assert!(!vm.gc_state.session_object_ptrs.contains(&dead_session));
|
||||||
assert!(!vm
|
assert!(!vm
|
||||||
.gc_state
|
.gc_state
|
||||||
@@ -262,39 +263,19 @@ fn sweep_preserves_array_elements_and_collects_dead_element_object() {
|
|||||||
let _ = gc.sweep(&mut vm);
|
let _ = gc.sweep(&mut vm);
|
||||||
vm.gc_state.session_gc = gc;
|
vm.gc_state.session_gc = gc;
|
||||||
|
|
||||||
// sweep 复制存活对象并把 VM 根改写为新 arena 指针:regs[0] 是数组的新地址,
|
// 原地 sweep 不搬移存活对象:数组保持原址、根指针不变,死元素对象
|
||||||
// 旧指针已随旧 arena 释放,不可再用。
|
// 原地释放并出表,存活集合只剩数组与元素 0 的对象。
|
||||||
let arr_new = vm.regs[0].as_js_object_ptr();
|
let arr_addr = vm.regs[0].as_js_object_ptr();
|
||||||
let live_session = unsafe { (*arr_new).get_prop_at(0).as_js_object_ptr() };
|
assert_eq!(arr_addr, arr_session, "存活数组原地保留,根指针不变");
|
||||||
|
let live_session = unsafe { (*arr_addr).get_prop_at(0).as_js_object_ptr() };
|
||||||
assert_eq!(unsafe { (*live_session).prop_count() }, 0);
|
assert_eq!(unsafe { (*live_session).prop_count() }, 0);
|
||||||
assert_eq!(unsafe { (*arr_new).prop_count() }, 2);
|
assert_eq!(unsafe { (*arr_addr).prop_count() }, 2);
|
||||||
assert_eq!(unsafe { (*arr_new).get_prop_at(0).as_js_object_ptr() }, live_session);
|
assert_eq!(unsafe { (*arr_addr).get_prop_at(0).as_js_object_ptr() }, live_session);
|
||||||
// 元素 1 引用已断开:死元素对象被回收,存活集合只剩数组与元素 0 的对象。
|
// 元素 1 引用已断开:死元素对象被回收。
|
||||||
assert_eq!(unsafe { (*arr_new).get_prop_at(1) }, JsValue::undefined());
|
assert_eq!(unsafe { (*arr_addr).get_prop_at(1) }, JsValue::undefined());
|
||||||
assert_eq!(vm.gc_state.session_object_ptrs.len(), 2);
|
assert_eq!(vm.gc_state.session_object_ptrs.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn forwarding_is_cleared_after_sweep() {
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
let root_session = vm.promote_object(root);
|
|
||||||
vm.regs[0] = JsValue::from_js_object(root_session);
|
|
||||||
let _ = vm.promote_object(child);
|
|
||||||
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
|
|
||||||
gc.mark(&vm);
|
|
||||||
let _ = gc.sweep(&mut vm);
|
|
||||||
vm.gc_state.session_gc = gc;
|
|
||||||
|
|
||||||
// 复用的 forwarding 表必须在 sweep 后清空,否则 promote 会观察到指向已释放
|
|
||||||
// arena 的过期 old->new 条目。
|
|
||||||
assert!(vm.gc_state.forwarding.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vm_with_low_threshold() -> Vm {
|
fn vm_with_low_threshold() -> Vm {
|
||||||
let mut cfg = KernelConfig::minimal();
|
let mut cfg = KernelConfig::minimal();
|
||||||
cfg.set_session_gc_threshold(1);
|
cfg.set_session_gc_threshold(1);
|
||||||
@@ -402,7 +383,7 @@ fn gc_stats_summary_includes_collection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn moving_sweep_rewrites_global_root_edges() {
|
fn full_collect_keeps_global_root_edges_in_place() {
|
||||||
let mut vm = vm_with_low_threshold();
|
let mut vm = vm_with_low_threshold();
|
||||||
let obj = plain_object(&mut vm);
|
let obj = plain_object(&mut vm);
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -426,7 +407,10 @@ fn moving_sweep_rewrites_global_root_edges() {
|
|||||||
.expect("global slot");
|
.expect("global slot");
|
||||||
let new_value = global.get_prop_at(pos);
|
let new_value = global.get_prop_at(pos);
|
||||||
assert!(new_value.is_object());
|
assert!(new_value.is_object());
|
||||||
assert!(!std::ptr::eq(new_value.as_js_object_ptr(), old_ptr));
|
assert!(
|
||||||
|
std::ptr::eq(new_value.as_js_object_ptr(), old_ptr),
|
||||||
|
"原地 sweep 不搬移对象:global 根边原址不变"
|
||||||
|
);
|
||||||
assert_eq!(unsafe { (*new_value.as_js_object_ptr()).get_prop_at(0) }, JsValue::int(42));
|
assert_eq!(unsafe { (*new_value.as_js_object_ptr()).get_prop_at(0) }, JsValue::int(42));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,8 +492,8 @@ fn weak_map_object(vm: &mut Vm) -> *mut JsObject {
|
|||||||
vm.alloc_object(obj)
|
vm.alloc_object(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 弱键不产 mark 边:强可达键跨完整收集保留,条目完整、键与值边改写
|
/// 弱键不产 mark 边:强可达键跨完整收集保留,条目完整、键与值边原地不变,
|
||||||
/// 到搬移后新址,经改写后的根读回同一克隆体。
|
/// 经原键读回同一对象。
|
||||||
#[test]
|
#[test]
|
||||||
fn weak_map_entry_survives_promotion_with_live_key() {
|
fn weak_map_entry_survives_promotion_with_live_key() {
|
||||||
let mut vm = vm_with_low_threshold();
|
let mut vm = vm_with_low_threshold();
|
||||||
@@ -540,13 +524,13 @@ fn weak_map_entry_survives_promotion_with_live_key() {
|
|||||||
|
|
||||||
let live_wm = unsafe { &*vm.regs[0].as_js_object_ptr() };
|
let live_wm = unsafe { &*vm.regs[0].as_js_object_ptr() };
|
||||||
assert_eq!(oxide_builtins::weak_map::weak_map_entry_count(live_wm), 1, "强可达键的条目须在收集后存活");
|
assert_eq!(oxide_builtins::weak_map::weak_map_entry_count(live_wm), 1, "强可达键的条目须在收集后存活");
|
||||||
// 键经移动式 sweep 搬移,条目键改指克隆体:按改写后的根读回。
|
// 原地 sweep 不搬移对象:键与值边原地保留,按原键读回同一对象。
|
||||||
let stored = oxide_builtins::weak_map::weak_map_probe_get(live_wm, vm.regs[1]);
|
let stored = oxide_builtins::weak_map::weak_map_probe_get(live_wm, vm.regs[1]);
|
||||||
assert_eq!(stored, vm.regs[2], "值须按改写后的键读回同一克隆体");
|
assert_eq!(stored, vm.regs[2], "值须按原键读回同一对象");
|
||||||
assert!(!std::ptr::eq(stored.as_js_object_ptr(), value), "值边须改写到搬移后新址");
|
assert!(std::ptr::eq(stored.as_js_object_ptr(), value), "值边原地不变");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 弱键强不可达 = 死键:收集按转发表判定丢条目,表不留死键残影。
|
/// 弱键强不可达 = 死键:收集按 mark 位定夺丢条目,表不留死键残影。
|
||||||
#[test]
|
#[test]
|
||||||
fn weak_map_entry_dropped_when_key_unrooted() {
|
fn weak_map_entry_dropped_when_key_unrooted() {
|
||||||
let mut vm = vm_with_low_threshold();
|
let mut vm = vm_with_low_threshold();
|
||||||
@@ -575,8 +559,7 @@ fn weak_map_entry_dropped_when_key_unrooted() {
|
|||||||
assert_eq!(oxide_builtins::weak_map::weak_map_entry_count(live_wm), 0, "死键条目须在收集时丢弃");
|
assert_eq!(oxide_builtins::weak_map::weak_map_entry_count(live_wm), 0, "死键条目须在收集时丢弃");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 值边为强边:值对象唯一引用仅来自值边时仍入存活集,搬移后条目按转发表
|
/// 值边为强边:值对象唯一引用仅来自值边时仍入存活集,原地保留原址。
|
||||||
/// 改写指到新址。
|
|
||||||
#[test]
|
#[test]
|
||||||
fn weak_map_value_edge_keeps_value_alive() {
|
fn weak_map_value_edge_keeps_value_alive() {
|
||||||
let mut vm = vm_with_low_threshold();
|
let mut vm = vm_with_low_threshold();
|
||||||
@@ -605,19 +588,18 @@ fn weak_map_value_edge_keeps_value_alive() {
|
|||||||
vm.gc_state.session_gc = gc;
|
vm.gc_state.session_gc = gc;
|
||||||
|
|
||||||
let live_wm = unsafe { &*vm.regs[0].as_js_object_ptr() };
|
let live_wm = unsafe { &*vm.regs[0].as_js_object_ptr() };
|
||||||
// 键经移动式 sweep 搬移,条目键改指克隆体:按改写后的根读回。
|
// 原地 sweep 不搬移对象:键与值边原地保留,按原键读回同一对象。
|
||||||
let stored = oxide_builtins::weak_map::weak_map_probe_get(live_wm, vm.regs[1]);
|
let stored = oxide_builtins::weak_map::weak_map_probe_get(live_wm, vm.regs[1]);
|
||||||
assert!(stored.is_object(), "唯一经值边引用的值须存活");
|
assert!(stored.is_object(), "唯一经值边引用的值须存活");
|
||||||
assert!(!std::ptr::eq(stored.as_js_object_ptr(), value_session), "值边须在搬移后改写到新址");
|
assert!(std::ptr::eq(stored.as_js_object_ptr(), value_session), "值边原地不变");
|
||||||
assert!(vm.is_session_ptr(stored.as_js_object_ptr()));
|
assert!(vm.is_session_ptr(stored.as_js_object_ptr()));
|
||||||
// key、value、wm 三对象均存活(key/value 经寄存器根、wm 经寄存器根),
|
// key、value、wm 三对象均存活(key/value 经寄存器根、wm 经寄存器根),
|
||||||
// 搬移后各占一份克隆。
|
// 原地保留原址。
|
||||||
assert_eq!(vm.gc_state.session_object_ptrs.len(), 3);
|
assert_eq!(vm.gc_state.session_object_ptrs.len(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// in-run 原地 sweep·死键面:无强根的 session 键在原地 sweep 判死出表;
|
/// in-run 原地 sweep·死键面:无强根的 session 键在原地 sweep 按 mark 位判死,
|
||||||
/// P 键不可死,条目保留且按原键读回。原地 sweep 不动弱表条目表,死键条目
|
/// 死键条目丢弃、死键对象释放出表;P 键不可死,条目保留且按原键读回。
|
||||||
/// 的定夺交后续单 mark 位判据,此处只钉死键对象出表 + P 键条目保留。
|
|
||||||
#[test]
|
#[test]
|
||||||
fn weak_map_dead_key_freed_by_in_run_sweep() {
|
fn weak_map_dead_key_freed_by_in_run_sweep() {
|
||||||
let mut vm = vm_with_threshold(65536);
|
let mut vm = vm_with_threshold(65536);
|
||||||
@@ -642,6 +624,11 @@ fn weak_map_dead_key_freed_by_in_run_sweep() {
|
|||||||
assert_eq!(vm.session_gc_stats().total_collections, 1, "in-run 收集应跑一轮");
|
assert_eq!(vm.session_gc_stats().total_collections, 1, "in-run 收集应跑一轮");
|
||||||
assert!(!vm.gc_state.session_object_ptrs.contains(&key), "死键对象须出表");
|
assert!(!vm.gc_state.session_object_ptrs.contains(&key), "死键对象须出表");
|
||||||
let live_wm = unsafe { &*vm.regs[0].as_js_object_ptr() };
|
let live_wm = unsafe { &*vm.regs[0].as_js_object_ptr() };
|
||||||
|
assert_eq!(
|
||||||
|
oxide_builtins::weak_map::weak_map_entry_count(live_wm),
|
||||||
|
1,
|
||||||
|
"死键条目须丢弃,仅 P 键条目保留"
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
oxide_builtins::weak_map::weak_map_probe_get(live_wm, p_key),
|
oxide_builtins::weak_map::weak_map_probe_get(live_wm, p_key),
|
||||||
JsValue::int(7),
|
JsValue::int(7),
|
||||||
@@ -1122,8 +1109,8 @@ fn global_prop_opt(vm: &Vm, name: &str) -> Option<JsValue> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 闭包捕获变量跨对象 sweep 存活:reset 触发完整收集(session_epoch 替换)。
|
/// 闭包捕获变量跨对象 sweep 存活:reset 触发完整收集(session_epoch 替换)。
|
||||||
/// cell 独立堆分配、不入 session arena,arena 整体回收时不连带释放——否则跨
|
/// cell 独立堆分配、地址稳定,原地 sweep 不搬移对象、不触碰 cell 结构——
|
||||||
/// arena 存活的 cell 指针悬垂;sweep 只重写 cell.value 中的对象引用,值跨搬移保留。
|
/// 跨收集的 cell 指针与值均保留。
|
||||||
#[test]
|
#[test]
|
||||||
fn closure_cell_survives_object_sweep() {
|
fn closure_cell_survives_object_sweep() {
|
||||||
let mut vm = vm_with_threshold(1);
|
let mut vm = vm_with_threshold(1);
|
||||||
@@ -1134,11 +1121,11 @@ fn closure_cell_survives_object_sweep() {
|
|||||||
.expect("run1");
|
.expect("run1");
|
||||||
assert!(!vm.gc_state.session_cell_ptrs.borrow().is_empty(), "run1 应分配 upvalue cell");
|
assert!(!vm.gc_state.session_cell_ptrs.borrow().is_empty(), "run1 应分配 upvalue cell");
|
||||||
|
|
||||||
// reset 触发对象 sweep:存活对象搬到新 arena,旧 arena 释放。
|
// reset 触发对象 sweep:存活对象原地保留,死对象原地释放。
|
||||||
vm.reset();
|
vm.reset();
|
||||||
assert!(vm.session_gc_stats().total_collections > 0, "reset 应触发对象收集");
|
assert!(vm.session_gc_stats().total_collections > 0, "reset 应触发对象收集");
|
||||||
|
|
||||||
// 跨搬移后从 global 重新取 inc 函数对象:upvalue cell 指针稳定、值保留。
|
// 收集后从 global 重新取 inc 函数对象:upvalue cell 指针稳定、值保留。
|
||||||
let inc_val = global_prop_opt(&vm, "inc").expect("inc 应挂在 global 上");
|
let inc_val = global_prop_opt(&vm, "inc").expect("inc 应挂在 global 上");
|
||||||
let obj = unsafe { &*inc_val.as_js_object_ptr() };
|
let obj = unsafe { &*inc_val.as_js_object_ptr() };
|
||||||
let cells = obj.upvalues_slice();
|
let cells = obj.upvalues_slice();
|
||||||
@@ -1149,8 +1136,8 @@ fn closure_cell_survives_object_sweep() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 私有字段类的 brand cell 跨对象 sweep 存活:`@@class_brand` upvalue cell
|
/// 私有字段类的 brand cell 跨对象 sweep 存活:`@@class_brand` upvalue cell
|
||||||
/// 存类 brand 对象,sweep 后其值经 forwarding 重写为搬移后的新对象地址。
|
/// 存类 brand 对象,原地 sweep 后其值为原 brand 对象(地址不变)。cell 独立
|
||||||
/// cell 独立堆分配,指针在周边 arena 回收后仍稳定,值不被内存复用覆盖。
|
/// 堆分配、地址稳定,原地 sweep 不触碰 cell 结构体。
|
||||||
#[test]
|
#[test]
|
||||||
fn private_brand_cell_survives_object_sweep() {
|
fn private_brand_cell_survives_object_sweep() {
|
||||||
let mut vm = vm_with_threshold(1);
|
let mut vm = vm_with_threshold(1);
|
||||||
@@ -1346,8 +1333,8 @@ impl Drop for NsCleanup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 模块 ns cell 根:依赖模块可重赋导出挂共享 cell(条目表 `Cell` 面),ns 对象
|
/// 模块 ns cell 根:依赖模块可重赋导出挂共享 cell(条目表 `Cell` 面),ns 对象
|
||||||
/// 经 global 可达;清寄存器仅留 ns 根后完整收集,cell 跨移动式 sweep 存活(深拷
|
/// 经 global 可达;清寄存器仅留 ns 根后完整收集,cell 跨原地 sweep 存活
|
||||||
/// 表保留同一 cell 指针),`module_ns_export` 读回正确。
|
/// (ns 对象与条目表原地不变),`module_ns_export` 读回正确。
|
||||||
#[test]
|
#[test]
|
||||||
fn module_ns_cell_root_survives_collect() {
|
fn module_ns_cell_root_survives_collect() {
|
||||||
let cwd = std::env::current_dir().expect("cwd");
|
let cwd = std::env::current_dir().expect("cwd");
|
||||||
@@ -1385,10 +1372,10 @@ fn module_ns_cell_root_survives_collect() {
|
|||||||
"模块 ns cell 应跨完整收集存活"
|
"模块 ns cell 应跨完整收集存活"
|
||||||
);
|
);
|
||||||
|
|
||||||
// 移动式 sweep 后 ns 晋升 session(原件出表),从 global 重取克隆读回。
|
// 原地 sweep 不搬移对象:ns 与条目表原地保留,从 global 重取读回。
|
||||||
let ns_val2 = global_prop_opt(&vm, "__ns").expect("__ns 应挂在 global 上");
|
let ns_val2 = global_prop_opt(&vm, "__ns").expect("__ns 应挂在 global 上");
|
||||||
let ns_obj2 = unsafe { &*ns_val2.as_js_object_ptr() };
|
let ns_obj2 = unsafe { &*ns_val2.as_js_object_ptr() };
|
||||||
assert!(module::module_ns_cell_edges(ns_obj2).contains(&cell_ptr), "克隆应共享同一 cell 指针");
|
assert!(module::module_ns_cell_edges(ns_obj2).contains(&cell_ptr), "ns 条目表应保留同一 cell 指针");
|
||||||
let name_si = vm.kernel_core().perm_interner().intern("x").0;
|
let name_si = vm.kernel_core().perm_interner().intern("x").0;
|
||||||
let query = module::module_ns_export(ns_obj2, name_si).expect("x 应可导出");
|
let query = module::module_ns_export(ns_obj2, name_si).expect("x 应可导出");
|
||||||
match query {
|
match query {
|
||||||
@@ -1705,12 +1692,14 @@ fn in_run_collection_reclaims_dead_and_keeps_live() {
|
|||||||
assert_eq!(a, Some(JsValue::int(1)));
|
assert_eq!(a, Some(JsValue::int(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// for-in 门控双形钉住:活跃形(迭代器在 `vm.iters` 表内)与挂起形
|
/// 无门控 for-in 双形钉住:活跃形(迭代器在 `vm.iters` 表内)与挂起形
|
||||||
/// (生成器在 for-in 内 yield,迭代器搬入状态盒)都须拦下执行期收集——
|
/// (生成器在 for-in 内 yield,迭代器搬入状态盒)存在时执行期收集照常执行
|
||||||
/// 门控是保守门,迭代器体为堆上 Box;门控开后方可收集。
|
/// ——迭代器键引用经根收集标活、原地清扫不搬移对象,无门控亦不误释放,
|
||||||
|
/// yield 值跨收集保持正确。
|
||||||
#[test]
|
#[test]
|
||||||
fn active_and_suspended_for_in_block_in_run_collection() {
|
fn active_and_suspended_for_in_do_not_block_in_run_collection() {
|
||||||
// 活跃形:迭代器直接压在 vm.iters,O(1) 子句即拦。
|
// 活跃形:迭代器直接压在 vm.iters,收集照常执行;死对象原地释放,
|
||||||
|
// 迭代器键经根收集标活。
|
||||||
let mut vm = vm_with_threshold(65536);
|
let mut vm = vm_with_threshold(65536);
|
||||||
let dead = plain_object(&mut vm);
|
let dead = plain_object(&mut vm);
|
||||||
let iter = Box::into_raw(Box::new(crate::vm::ForInIter {
|
let iter = Box::into_raw(Box::new(crate::vm::ForInIter {
|
||||||
@@ -1720,17 +1709,11 @@ fn active_and_suspended_for_in_block_in_run_collection() {
|
|||||||
vm.iters.push_for_in(iter);
|
vm.iters.push_for_in(iter);
|
||||||
|
|
||||||
vm.maybe_collect_in_run();
|
vm.maybe_collect_in_run();
|
||||||
assert_eq!(vm.session_gc_stats().total_collections, 0, "活跃 for-in:门控关闭,不收集");
|
assert_eq!(vm.session_gc_stats().total_collections, 1, "无门控:活跃 for-in 存在时收集照常执行");
|
||||||
assert!(vm.gc_state.session_object_ptrs.contains(&dead), "死对象仍在 session 表");
|
|
||||||
|
|
||||||
// 迭代器出表后同一调用点即应收集。
|
|
||||||
vm.iters.pop_for_in();
|
|
||||||
vm.maybe_collect_in_run();
|
|
||||||
assert_eq!(vm.session_gc_stats().total_collections, 1, "门控开:应收集");
|
|
||||||
assert!(!vm.gc_state.session_object_ptrs.contains(&dead), "死 session 对象应被原地释放并出表");
|
assert!(!vm.gc_state.session_object_ptrs.contains(&dead), "死 session 对象应被原地释放并出表");
|
||||||
|
|
||||||
// 挂起形(拦截):生成器在 for-in 内 yield 后 run 结束,迭代器经
|
// 挂起形:生成器在 for-in 内 yield 后 run 结束,迭代器经状态盒持有
|
||||||
// 状态盒持有(vm.iters 已空)——收集点必须拦下。
|
// (vm.iters 已空)——键经状态盒边入根收集标活,收集照常执行。
|
||||||
let mut vm = vm_with_threshold(65536);
|
let mut vm = vm_with_threshold(65536);
|
||||||
vm.run(&Arc::new(compile(
|
vm.run(&Arc::new(compile(
|
||||||
"var o = { a: 1, b: 2 }; \
|
"var o = { a: 1, b: 2 }; \
|
||||||
@@ -1739,10 +1722,9 @@ fn active_and_suspended_for_in_block_in_run_collection() {
|
|||||||
)))
|
)))
|
||||||
.expect("run1");
|
.expect("run1");
|
||||||
assert!(vm.iters.for_in_iters.is_empty(), "挂起时迭代器已搬入状态盒");
|
assert!(vm.iters.for_in_iters.is_empty(), "挂起时迭代器已搬入状态盒");
|
||||||
assert!(vm.suspended_holds_for_in(), "生成器状态盒应持挂起 for-in 迭代器");
|
|
||||||
|
|
||||||
vm.maybe_collect_in_run();
|
vm.maybe_collect_in_run();
|
||||||
assert_eq!(vm.session_gc_stats().total_collections, 0, "挂起 for-in:门控关闭,不收集");
|
assert_eq!(vm.session_gc_stats().total_collections, 1, "挂起 for-in:收集照常执行");
|
||||||
// 挂起生成器对象保持原址(未释放):状态盒与迭代器一体存活。
|
// 挂起生成器对象保持原址(未释放):状态盒与迭代器一体存活。
|
||||||
let gen_in_session = vm
|
let gen_in_session = vm
|
||||||
.gc_state
|
.gc_state
|
||||||
@@ -1751,9 +1733,8 @@ fn active_and_suspended_for_in_block_in_run_collection() {
|
|||||||
.any(|&p| !p.is_null() && unsafe { (*p).type_tag == JsObject::OBJ_TYPE_GENERATOR });
|
.any(|&p| !p.is_null() && unsafe { (*p).type_tag == JsObject::OBJ_TYPE_GENERATOR });
|
||||||
assert!(gen_in_session, "挂起生成器应仍在 session 表");
|
assert!(gen_in_session, "挂起生成器应仍在 session 表");
|
||||||
|
|
||||||
// 挂起形(放行):同一 run 内续跑至 for-in 结束(迭代器释放),
|
// 同一 run 内续跑至 for-in 结束(迭代器释放),阈值 1 使指令边界自动触发
|
||||||
// 阈值 1 使指令边界自动触发:挂起期门控关(Bump 不换新),完成后
|
// 收集:挂起期与完成期收集均照常放行,yield 值跨收集保持正确。
|
||||||
// 门控开、执行期收集放行;yield 值跨收集保持正确。
|
|
||||||
let mut vm = vm_with_threshold(1);
|
let mut vm = vm_with_threshold(1);
|
||||||
vm.run(&Arc::new(compile(
|
vm.run(&Arc::new(compile(
|
||||||
"var o = { a: 1, b: 2 }; \
|
"var o = { a: 1, b: 2 }; \
|
||||||
@@ -1764,10 +1745,9 @@ fn active_and_suspended_for_in_block_in_run_collection() {
|
|||||||
.expect("run2");
|
.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, "v1").expect("v1")), Some("a".to_string()));
|
||||||
assert_eq!(vm.lookup_str(global_prop_opt(&vm, "v2").expect("v2")), Some("b".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, "执行期收集应已放行");
|
||||||
assert!(vm.session_gc_stats().total_collections >= 1, "完成后门控开:执行期收集应放行");
|
|
||||||
|
|
||||||
// 生成器对象跨收集仍为合法生成器(晋升 session 或保持 epoch 完好)。
|
// 生成器对象跨收集仍为合法生成器(session 对象原地保留)。
|
||||||
let g = global_prop_opt(&vm, "g").expect("g 应挂在 global");
|
let g = global_prop_opt(&vm, "g").expect("g 应挂在 global");
|
||||||
assert!(unsafe { (*g.as_js_object_ptr()).is_generator_obj() }, "收集后生成器对象应完好");
|
assert!(unsafe { (*g.as_js_object_ptr()).is_generator_obj() }, "收集后生成器对象应完好");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ impl Vm {
|
|||||||
// 返回顶层后检查恢复,存活值此时已回拷为执行根。账目未超水位时仅
|
// 返回顶层后检查恢复,存活值此时已回拷为执行根。账目未超水位时仅
|
||||||
// 少量字段比较。
|
// 少量字段比较。
|
||||||
if self.native_call_depth == 0 {
|
if self.native_call_depth == 0 {
|
||||||
// 宿主 `$262.gc()` 置位的强制收集在此边界消费:移动式 sweep
|
// 宿主 `$262.gc()` 置位的强制收集在此边界消费:完整收集
|
||||||
// 唯一安全点(无在途 dispatch 局部值),与正常触发路径同一入口。
|
// 唯一安全点(无在途 dispatch 局部值),与正常触发路径同一入口。
|
||||||
if self.gc_state.pending_forced_collect {
|
if self.gc_state.pending_forced_collect {
|
||||||
self.gc_state.pending_forced_collect = false;
|
self.gc_state.pending_forced_collect = false;
|
||||||
@@ -142,8 +142,9 @@ impl Vm {
|
|||||||
if bytes >= self.gc_state.string_gc_watermark {
|
if bytes >= self.gc_state.string_gc_watermark {
|
||||||
self.maybe_collect_session_strings();
|
self.maybe_collect_session_strings();
|
||||||
}
|
}
|
||||||
// 执行期两档收集:O(1) 包络超触发水位 → 门控(活跃/挂起 for-in)
|
// 执行期原地 sweep 收集:O(1) 包络超触发水位 → 收集(无门控:
|
||||||
// + epoch 晋升收集 + session 原地 sweep(边界契约见 maybe_collect_in_run)。
|
// 迭代器键引用经根收集标活,原地释放不搬移存活对象;
|
||||||
|
// 边界契约见 maybe_collect_in_run)。
|
||||||
if alloc >= self.gc_state.gc_watermark {
|
if alloc >= self.gc_state.gc_watermark {
|
||||||
self.maybe_collect_in_run();
|
self.maybe_collect_in_run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,60 +239,17 @@ impl Vm {
|
|||||||
self.gc_state.session_gc = session_gc;
|
self.gc_state.session_gc = session_gc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 门控子句二:三型状态盒(生成器/异步函数/异步生成器)的
|
/// 执行期原地 sweep 收集的 dispatch 安全点入口:仅在循环顶
|
||||||
/// `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 安全点入口:仅在循环顶
|
|
||||||
/// (`native_call_depth == 0`,无 builtin 局部裸指针、dispatch 未重入)
|
/// (`native_call_depth == 0`,无 builtin 局部裸指针、dispatch 未重入)
|
||||||
/// 由触发块调用;先门控后收集。
|
/// 由触发块在水位命中后调用,无门控直接收集。
|
||||||
///
|
///
|
||||||
/// # 边界与前提
|
/// # 边界与前提
|
||||||
/// - 子句一(活跃 for-in,O(1))未过:免费重试,不设锚;
|
/// - for-in 迭代器体是堆上 Box:活跃形经 VM 表、挂起形经状态盒边进入
|
||||||
/// - 子句二(状态盒扫描,O(对象数))未过:重扫按包络增长一个阈值设锚,
|
/// 根收集,键引用被 mark 标活,原地清扫不搬移对象、不误释放,
|
||||||
/// 避免持挂起 for-in 的 run 每指令边界重扫;
|
/// 无需拦截;
|
||||||
/// - 门控过但无对象可回收时仍跑一轮(mark + 换新 Bump),水位同点抬高,
|
/// - 无对象可回收时仍跑一轮(mark + 原地清扫),水位同点抬高,
|
||||||
/// 触发间距由包络增量控制。
|
/// 触发间距由包络增量控制。
|
||||||
pub(crate) fn maybe_collect_in_run(&mut self) {
|
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);
|
let mut session_gc = std::mem::take(&mut self.gc_state.session_gc);
|
||||||
session_gc.collect_in_run(self);
|
session_gc.collect_in_run(self);
|
||||||
self.gc_state.session_gc = session_gc;
|
self.gc_state.session_gc = session_gc;
|
||||||
@@ -373,10 +330,10 @@ impl Vm {
|
|||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 无条件执行一次完整 session GC(mark + 移动式 sweep + 串/BigInt 清扫)。
|
/// 无条件执行一次完整 session GC(mark + 对象原地清扫 + 串/BigInt 清扫)。
|
||||||
///
|
///
|
||||||
/// # 副作用
|
/// # 副作用
|
||||||
/// - 存活对象复制进新 session arena,全部根与原生盒按转发表重写;
|
/// - 存活对象地址不变、根与原生盒免改写;
|
||||||
/// `session_bytes_allocated` 重置为清扫后的存活字节。
|
/// `session_bytes_allocated` 重置为清扫后的存活字节。
|
||||||
///
|
///
|
||||||
/// # 注意事项
|
/// # 注意事项
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use rustc_hash::FxBuildHasher;
|
|
||||||
|
|
||||||
use crate::session_gc::SessionGc;
|
use crate::session_gc::SessionGc;
|
||||||
use crate::vm::ForInIter;
|
use crate::vm::ForInIter;
|
||||||
use oxide_builtins::iterator::BuiltinIterKind;
|
use oxide_builtins::iterator::BuiltinIterKind;
|
||||||
@@ -35,9 +33,9 @@ pub(crate) struct GcState {
|
|||||||
/// mark/sweep 回收(值量少),只在 full_reset 统一释放。
|
/// mark/sweep 回收(值量少),只在 full_reset 统一释放。
|
||||||
pub(crate) session_bigint_ptrs: RefCell<Vec<*mut num_bigint::BigInt>>,
|
pub(crate) session_bigint_ptrs: RefCell<Vec<*mut num_bigint::BigInt>>,
|
||||||
/// upvalue cell(`Box<Cell>`)追踪表。`RefCell` 使 `&self` 的分配入口也能
|
/// upvalue cell(`Box<Cell>`)追踪表。`RefCell` 使 `&self` 的分配入口也能
|
||||||
/// 登记新 box。cell 独立堆分配、地址稳定,不参与对象搬移(sweep 只重写
|
/// 登记新 box。cell 独立堆分配、地址稳定,不参与对象搬移(原地 sweep
|
||||||
/// `cell.value` 中的对象引用);参与 mark-sweep 回收(死 cell 随收集释放),
|
/// 不触碰 cell,也不重写 `cell.value`);参与 mark-sweep 回收(死 cell
|
||||||
/// full_reset 为收尾兜底(表已空时 no-op)。
|
/// 随收集释放),full_reset 为收尾兜底(表已空时 no-op)。
|
||||||
pub(crate) session_cell_ptrs: RefCell<Vec<*mut UpvalueCell>>,
|
pub(crate) session_cell_ptrs: RefCell<Vec<*mut UpvalueCell>>,
|
||||||
pub(crate) session_bytes_allocated: usize,
|
pub(crate) session_bytes_allocated: usize,
|
||||||
/// 执行期 session 堆账目的峰值高水位(`session_bytes_allocated` 的采样上界),
|
/// 执行期 session 堆账目的峰值高水位(`session_bytes_allocated` 的采样上界),
|
||||||
@@ -52,24 +50,21 @@ pub(crate) struct GcState {
|
|||||||
pub(crate) string_gc_watermark: usize,
|
pub(crate) string_gc_watermark: usize,
|
||||||
/// 缓存的 GC 阈值(从 config 读一次),热路径只做 usize 比较,免 Arc 解引用。
|
/// 缓存的 GC 阈值(从 config 读一次),热路径只做 usize 比较,免 Arc 解引用。
|
||||||
pub(crate) gc_threshold_cached: usize,
|
pub(crate) gc_threshold_cached: usize,
|
||||||
/// 执行期两档收集(epoch 晋升 + session 原地 sweep)的触发水位:本次
|
/// 执行期原地 sweep 收集的触发水位:本次收集后的分配包络 + 阈值增量。
|
||||||
/// 收集后的分配包络 + 阈值增量。仅当 `run_alloc_bytes` 超过水位才在指令
|
/// 仅当 `run_alloc_bytes` 超过水位才在指令边界触发——存活包络超阈值时
|
||||||
/// 边界触发——存活包络超阈值时不每指令重复触发无死对象可回收的白跑。
|
/// 不每指令重复触发无死对象可回收的白跑。
|
||||||
pub(crate) gc_watermark: usize,
|
pub(crate) gc_watermark: usize,
|
||||||
/// 门控未过(挂起态持有 for-in)后的重扫锚:包络再增长一个阈值才重扫
|
|
||||||
/// 三型状态盒(O(对象数) 扫描),避免持挂起 for-in 的 run 每指令边界重扫。
|
|
||||||
pub(crate) gc_gate_retry_alloc: usize,
|
|
||||||
/// 宿主强制收集旗标(`$262.gc()`):native 重入中置位,由下一个顶层
|
/// 宿主强制收集旗标(`$262.gc()`):native 重入中置位,由下一个顶层
|
||||||
/// 指令边界(移动式 sweep 的唯一安全点)消费并执行完整收集。
|
/// 指令边界(完整收集的唯一安全点:无在途 builtin 局部裸指针)消费。
|
||||||
pub(crate) pending_forced_collect: bool,
|
pub(crate) pending_forced_collect: bool,
|
||||||
pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GcState {
|
impl GcState {
|
||||||
/// 分配一个 upvalue cell:独立堆分配并返回裸指针,脱离 session arena 生命周期。
|
/// 分配一个 upvalue cell:独立堆分配并返回裸指针,脱离 session arena 生命周期。
|
||||||
///
|
///
|
||||||
/// cell 指针登记进 `session_cell_ptrs`,在 `full_reset` 统一释放。对象 sweep
|
/// cell 指针登记进 `session_cell_ptrs`,在 `full_reset` 统一释放。对象
|
||||||
/// 搬移不触碰 cell 结构体(地址稳定),只重写 `cell.value` 中的对象引用。
|
/// sweep 原地化不搬移对象、不触碰 cell 结构体(地址稳定),cell 的
|
||||||
|
/// 生死由 mark-sweep 自行裁定(死 cell 随收集释放)。
|
||||||
/// `&self` 使调用方可与 `cell_stack` 等其它字段的借用并存(分字段借用)。
|
/// `&self` 使调用方可与 `cell_stack` 等其它字段的借用并存(分字段借用)。
|
||||||
pub(crate) fn alloc_cell(&self, value: JsValue, initialized: bool) -> *mut UpvalueCell {
|
pub(crate) fn alloc_cell(&self, value: JsValue, initialized: bool) -> *mut UpvalueCell {
|
||||||
let ptr = Box::into_raw(Box::new(UpvalueCell::new(value, initialized)));
|
let ptr = Box::into_raw(Box::new(UpvalueCell::new(value, initialized)));
|
||||||
|
|||||||
@@ -134,9 +134,7 @@ impl Vm {
|
|||||||
string_gc_watermark: gc_threshold,
|
string_gc_watermark: gc_threshold,
|
||||||
gc_threshold_cached: gc_threshold,
|
gc_threshold_cached: gc_threshold,
|
||||||
gc_watermark: gc_threshold,
|
gc_watermark: gc_threshold,
|
||||||
gc_gate_retry_alloc: 0,
|
|
||||||
pending_forced_collect: false,
|
pending_forced_collect: false,
|
||||||
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
|
|
||||||
},
|
},
|
||||||
symbols: SymbolState {
|
symbols: SymbolState {
|
||||||
symbol_counter: 0,
|
symbol_counter: 0,
|
||||||
@@ -271,9 +269,7 @@ impl Vm {
|
|||||||
string_gc_watermark: gc_threshold,
|
string_gc_watermark: gc_threshold,
|
||||||
gc_threshold_cached: gc_threshold,
|
gc_threshold_cached: gc_threshold,
|
||||||
gc_watermark: gc_threshold,
|
gc_watermark: gc_threshold,
|
||||||
gc_gate_retry_alloc: 0,
|
|
||||||
pending_forced_collect: false,
|
pending_forced_collect: false,
|
||||||
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
|
|
||||||
},
|
},
|
||||||
symbols: SymbolState {
|
symbols: SymbolState {
|
||||||
symbol_counter: 0,
|
symbol_counter: 0,
|
||||||
@@ -408,65 +404,42 @@ impl Vm {
|
|||||||
self.gc_state.session_bytes_peak = 0;
|
self.gc_state.session_bytes_peak = 0;
|
||||||
self.gc_state.run_alloc_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.string_gc_watermark = self.kernel_core.config().session_gc_threshold;
|
||||||
// 两档收集水位与门控重扫锚同点复位(同式:阈值增量起算)。
|
// 执行期收集水位同点复位(同式:阈值增量起算):旧 run 的存活包络
|
||||||
|
// 不延续到新 run 的触发判定。
|
||||||
self.gc_state.gc_watermark = self.gc_state.gc_threshold_cached;
|
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.gc_state.session_gc = crate::session_gc::SessionGc::new();
|
||||||
self.symbols.reset();
|
self.symbols.reset();
|
||||||
self.root_reg_limit = 0;
|
self.root_reg_limit = 0;
|
||||||
self.active_reg_limit = 0;
|
self.active_reg_limit = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 释放全部 session 堆数据:session 对象堆数据 + upvalue 列表 + 对象本体
|
/// 释放全部 session 堆数据:session 对象(本体 + 堆数据 + upvalue 列表)
|
||||||
/// + session 串 + BigInt box + upvalue cell box。
|
/// + session 串 + BigInt box + upvalue cell box。
|
||||||
///
|
///
|
||||||
/// 供 `full_reset` 与 `Drop` 共用——堆载体对象本体(HEAP_BIT)经
|
/// 供 `full_reset` 与 `Drop` 共用——统一入口路径上对象本体全部为堆载体
|
||||||
/// `Box::from_raw` 释放,arena 载体(克隆)随调用方换新的 session Bump 归还,
|
/// (`Box::from_raw` 恰好释放一次;晋升族 Bump 克隆仅测试形态,本体随
|
||||||
/// 本函数不释放。原生盒在 GC 搬移/晋升时已深拷贝为单所有权,此处恰好释放一次。
|
/// 换新 Bump 归还),堆数据与 upvalue 列表各恰好释放一次。独占所有权
|
||||||
|
/// 免去重:死对象已出表不重复枚举,表内无共享 upvalue Box 的原件-克隆对
|
||||||
|
/// (死对象从不被克隆,生产路径不产克隆)。
|
||||||
pub(crate) fn teardown_session_heap_data(&mut self) {
|
pub(crate) fn teardown_session_heap_data(&mut self) {
|
||||||
// 迭代器体是堆上 Box:收尾路径(full_reset 与 Drop 共用)逐条释放,
|
// 迭代器体是堆上 Box:收尾路径(full_reset 与 Drop 共用)逐条释放,
|
||||||
// 防 Vm 直接 drop 时表内残留体泄漏。
|
// 防 Vm 直接 drop 时表内残留体泄漏。
|
||||||
self.iters.reset();
|
self.iters.reset();
|
||||||
// upvalue 列表先于对象表清空释放:去重枚举依赖对象表尚存。
|
// 对象逐条独占释放:本体 + 堆数据 + upvalue 列表,顺序由
|
||||||
self.free_session_upvalues();
|
// drop_dead_session_object 收口。
|
||||||
for ptr in self.gc_state.session_object_ptrs.drain(..) {
|
for ptr in self.gc_state.session_object_ptrs.drain(..) {
|
||||||
crate::session_gc::SessionGc::drop_object_heap_data(ptr, true);
|
if ptr.is_null() {
|
||||||
// 释放对象本体:堆载体(HEAP_BIT)经 Box::from_raw 释放,arena
|
continue;
|
||||||
// 载体(克隆)随换新 Bump 归还,不在此释放。
|
|
||||||
if !ptr.is_null() && unsafe { (*ptr).is_heap_alloc() } {
|
|
||||||
unsafe {
|
|
||||||
drop(Box::from_raw(ptr));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// SAFETY: ptr 来自 session 对象表登记,收尾时仍指向合法对象;
|
||||||
|
// 表独占持有,无其他释放点。
|
||||||
|
crate::session_gc::SessionGc::drop_dead_session_object(ptr);
|
||||||
}
|
}
|
||||||
self.free_session_string_heap_data();
|
self.free_session_string_heap_data();
|
||||||
self.free_session_bigint_heap_data();
|
self.free_session_bigint_heap_data();
|
||||||
self.gc_state.free_cells();
|
self.gc_state.free_cells();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 集中释放 upvalue 列表(`Box<Vec<*mut Cell>>`):原件与晋升克隆经
|
|
||||||
/// `clone_for_session_epoch` 共享同一 Box 分配,逐对象路径释放会双放,
|
|
||||||
/// 只在此处按指针去重后统一释放。须在对象表清空之前调用
|
|
||||||
/// (枚举仍存对象完成去重),此时对象已死、Box 无其他读者。
|
|
||||||
fn free_session_upvalues(&mut self) {
|
|
||||||
let mut seen = std::collections::HashSet::new();
|
|
||||||
for &ptr in self.gc_state.session_object_ptrs.iter() {
|
|
||||||
if ptr.is_null() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// SAFETY: ptr 来自 session 对象表登记,收尾时仍指向合法对象。
|
|
||||||
let up = unsafe { (*ptr).upvalues };
|
|
||||||
if up.is_null() || !seen.insert(up) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// SAFETY: up 由 set_upvalues 的 Box::into_raw 分配,去重保证恰好释放一次。
|
|
||||||
unsafe {
|
|
||||||
drop(Box::from_raw(up as *mut Vec<*mut oxide_types::object::Cell>));
|
|
||||||
(*ptr).upvalues = std::ptr::null_mut();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 释放 VM 内建原型 P 对象(生成器/Promise/异步族与 Object 原型)的堆外属性区。
|
/// 释放 VM 内建原型 P 对象(生成器/Promise/异步族与 Object 原型)的堆外属性区。
|
||||||
///
|
///
|
||||||
/// # 注意事项
|
/// # 注意事项
|
||||||
@@ -576,8 +549,8 @@ 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();
|
||||||
// session 对象可持有 epoch 子引用(函数对象捕获、原生盒直插):
|
// 晋升族空转调用保留:统一入口 Box 化后生产路径不产 epoch 对象,
|
||||||
// 把 epoch 子引用原地克隆晋升进 session,避免悬垂指针。
|
// 本调用在统一表上恒无操作(随晋升族整体退役)。
|
||||||
self.promote_session_epoch_refs();
|
self.promote_session_epoch_refs();
|
||||||
self.bytecode = Arc::default();
|
self.bytecode = Arc::default();
|
||||||
// 表代际注册表不动:存活函数对象(含挂起帧 callee)按创建期代际仍须
|
// 表代际注册表不动:存活函数对象(含挂起帧 callee)按创建期代际仍须
|
||||||
@@ -586,9 +559,8 @@ impl Vm {
|
|||||||
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
|
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
|
||||||
// 单 run 分配包络按 run 边界重起算(与 run_alloc_bytes 起算口径同源)。
|
// 单 run 分配包络按 run 边界重起算(与 run_alloc_bytes 起算口径同源)。
|
||||||
self.gc_state.run_alloc_peak = 0;
|
self.gc_state.run_alloc_peak = 0;
|
||||||
// 两档收集水位与包络同起算:旧 run 的存活包络不延续到新 run 的触发判定。
|
// 执行期收集水位与包络同起算:旧 run 的存活包络不延续到新 run 的触发判定。
|
||||||
self.gc_state.gc_watermark = self.gc_state.gc_threshold_cached;
|
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.root_reg_limit = 0;
|
||||||
self.active_reg_limit = 0;
|
self.active_reg_limit = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,8 +225,8 @@ fn reentry_pump_hits_alloc_cap() {
|
|||||||
assert!(err.contains("memory limit"), "unexpected error: {err}");
|
assert!(err.contains("memory limit"), "unexpected error: {err}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 直接恢复生成器一步:等价 `it.next()`——回归聚焦 GC 搬移后的状态盒
|
/// 直接恢复生成器一步:等价 `it.next()`——回归聚焦 GC 收集后的状态盒
|
||||||
/// 有效性(同 run 内恢复,不经 run 边界换表)。
|
/// 有效性(原地 sweep 不搬移对象;同 run 内恢复,不经 run 边界换表)。
|
||||||
fn resume_one_step(vm: &mut Vm, gen: JsValue) -> JsValue {
|
fn resume_one_step(vm: &mut Vm, gen: JsValue) -> JsValue {
|
||||||
match vm.resume_generator(gen, crate::generator::GeneratorResumeMode::Next(JsValue::undefined())) {
|
match vm.resume_generator(gen, crate::generator::GeneratorResumeMode::Next(JsValue::undefined())) {
|
||||||
Ok(crate::generator::GeneratorStep::Suspended { value }) => value,
|
Ok(crate::generator::GeneratorStep::Suspended { value }) => value,
|
||||||
@@ -248,12 +248,12 @@ fn generator_survives_object_sweep_and_resumes() {
|
|||||||
let mut vm = vm_with_low_threshold();
|
let mut vm = vm_with_low_threshold();
|
||||||
let _ = run_source(&mut vm, "function* g(){ yield 1; yield 2; } globalThis.it = g(); globalThis.it.next(); 0");
|
let _ = run_source(&mut vm, "function* g(){ yield 1; yield 2; } globalThis.it = g(); globalThis.it.next(); 0");
|
||||||
|
|
||||||
// 直接触发完整收集(保留执行上下文):存活生成器克隆进新 arena,
|
// 直接触发完整收集(保留执行上下文):存活生成器原地保留、
|
||||||
// 状态盒深拷贝为新 Box(走收集入口而非 run 边界:聚焦 GC 搬移本身)。
|
// 状态盒不动(走收集入口而非 run 边界:聚焦原地收集本身)。
|
||||||
vm.maybe_collect_session_gc();
|
vm.maybe_collect_session_gc();
|
||||||
assert!(vm.session_gc_stats().total_collections > 0, "应触发对象收集");
|
assert!(vm.session_gc_stats().total_collections > 0, "应触发对象收集");
|
||||||
|
|
||||||
// 从 global 取 sweep 重写后的生成器(同 run,模块表未换发,可恢复)。
|
// 从 global 取 sweep 后存活的生成器(同 run,模块表未换发,可恢复)。
|
||||||
let it = global_prop(&vm, "it");
|
let it = global_prop(&vm, "it");
|
||||||
assert_eq!(resume_one_step(&mut vm, it), JsValue::int(2), "sweep 后应恢复第二次 yield");
|
assert_eq!(resume_one_step(&mut vm, it), JsValue::int(2), "sweep 后应恢复第二次 yield");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//! 生成器 × session GC 回归:promote 后 full_reset 释放 epoch 原对象与 session 克隆
|
//! 生成器 × session GC 回归:full_reset 逐对象释放 session 生成器(本体 + 堆数据
|
||||||
//! 的堆数据(各单所有权),不 double-free、不悬垂,VM 继续可用。
|
//! + upvalue 列表,各单所有权),不 double-free、不悬垂,VM 继续可用。
|
||||||
//! sweep 变体与闭包捕获变体见 `vm_support.rs` 内部测试(需 crate 内 `resume_generator`
|
//! sweep 变体与闭包捕获变体见 `vm_support.rs` 内部测试(需 crate 内 `resume_generator`
|
||||||
//! 在模块表未重建时恢复执行,integration 层无法触达)。
|
//! 在模块表未重建时恢复执行,integration 层无法触达)。
|
||||||
|
|
||||||
@@ -15,8 +15,8 @@ fn compile(source: &str) -> oxide_bytecode::module::CompiledModule {
|
|||||||
Compiler::new().compile(&program).expect("compile")
|
Compiler::new().compile(&program).expect("compile")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// promote + full_reset 变体:global 根持有生成器克隆,full_reset 释放 epoch 原对象
|
/// full_reset 变体:global 根持有生成器,full_reset 释放其本体、堆数据与
|
||||||
/// 与 session 克隆的堆数据(各单所有权),不 double-free、不悬垂,VM 继续可用。
|
/// upvalue 列表(各单所有权),不 double-free、不悬垂,VM 继续可用。
|
||||||
#[test]
|
#[test]
|
||||||
fn generator_promoted_to_global_survives_full_reset_cleanly() {
|
fn generator_promoted_to_global_survives_full_reset_cleanly() {
|
||||||
let mut vm = Vm::new();
|
let mut vm = Vm::new();
|
||||||
@@ -24,10 +24,9 @@ fn generator_promoted_to_global_survives_full_reset_cleanly() {
|
|||||||
"function* g(){ yield 1; yield 2; } globalThis.it = g(); globalThis.it.next(); 0",
|
"function* g(){ yield 1; yield 2; } globalThis.it = g(); globalThis.it.next(); 0",
|
||||||
)))
|
)))
|
||||||
.expect("run1");
|
.expect("run1");
|
||||||
assert!(vm.session_object_count() > 0, "生成器挂 global 应被 promote 进 session");
|
assert!(vm.session_object_count() > 0, "生成器挂 global 应为 session 对象");
|
||||||
|
|
||||||
// global 含 session 对象即强制重建:旧 global 丢弃、it 槽不存在,全程无悬垂访问
|
// global 含 session 对象即强制重建:旧 global 丢弃、it 槽不存在,全程无悬垂访问。
|
||||||
// (修复前 epoch 原对象释放共享状态盒 → 读已释放内存)。
|
|
||||||
vm.full_reset();
|
vm.full_reset();
|
||||||
assert_eq!(vm.session_object_count(), 0, "full_reset 应清空 session 对象");
|
assert_eq!(vm.session_object_count(), 0, "full_reset 应清空 session 对象");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user