Files
OxideJS-New/crates/oxide_vm/src/vm/gc_hooks.rs
T

422 lines
18 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! GC 根遍历与执行期收集钩子:根统一枚举、搬移后指针重写、执行期两档
//! 收集入口(安全点门控)与 session GC 账目统计访问器。
use oxide_types::object::{Cell, JsObject};
use oxide_types::value::JsValue;
use super::frames::Completion;
use super::Vm;
use crate::session_gc::SessionGc;
impl Vm {
/// GC 根收集的统一遍历(对象与字符串都产出)。与 `rewrite_values` 字段一一对应。
/// 覆盖执行核心的全部 JsValue 持有点:regs/帧/各栈段/cell/在途异常与完成/
/// 挂起信号/迭代器/微任务/global。新增执行字段必须同时登记在此与
/// `rewrite_values`。
pub(crate) fn for_each_value(&self, mut f: impl FnMut(JsValue)) {
for value in &self.regs {
f(*value);
}
// 各代际 immutables 缓存含 session BigInt(new_bigint 分配),未入根则被
// sweep 释放 → 常量池加载时悬垂。perm 字符串无害(不在 session 集合中)。
for table in self.tables.values() {
for once_lock in &table.immutables {
if let Some(immutable_vec) = once_lock.get() {
for &value in immutable_vec.iter() {
f(value);
}
}
}
}
for frame in &self.frames {
f(frame.saved_this);
f(frame.saved_new_target);
f(frame.callee);
f(frame.constructed_this.unwrap_or(JsValue::undefined()));
}
for &v in &self.save_stack {
f(v);
}
// spill 栈是 session GC 根(漏根 → 溢出值被回收 → use-after-free)。
for &v in &self.spill_stack {
f(v);
}
for cell_vec in &self.cell_stack {
for &cell_ptr in cell_vec {
if cell_ptr.is_null() {
continue;
}
// SAFETY: cell 由 session_epoch 分配,本 session 内指针有效。
f(unsafe { &*cell_ptr }.value);
}
}
f(self.exception_value.unwrap_or(JsValue::undefined()));
f(self.pending_exception.unwrap_or(JsValue::undefined()));
f(self.last_uncaught_value.unwrap_or(JsValue::undefined()));
f(self.pending_length_exception.unwrap_or(JsValue::undefined()));
// 悬挂的 return 完成持有返回值,是 GC 根。
if let Some(Completion::Return { value, .. }) = self.pending_completion {
f(value);
}
f(self.generator_suspended.unwrap_or(JsValue::undefined()));
f(self.delegated_iterator.unwrap_or(JsValue::undefined()));
f(self.async_context.unwrap_or(JsValue::undefined()));
f(self.async_gen_context.unwrap_or(JsValue::undefined()));
// 在途异步逃出的 promise/完成值/剩余迭代器都是 GC 根。
if let Some(pend) = &self.pending_async_escape {
f(pend.close_promise);
if let Completion::Return { value, .. } = pend.completion {
f(value);
}
for &v in &pend.remaining {
f(v);
}
}
f(self.inline_callee.unwrap_or(JsValue::undefined()));
// 标签模板对象缓存:命中的模板对象是 GC 根(未根 → sweep 搬移/回收悬垂)。
for &cached in self.template_objects.values() {
f(cached);
}
for &entry in &self.iters.for_of_iters {
f(entry.iterator);
f(entry.last_result);
f(entry.fast_value);
}
// 微任务队列中的处理器/能力/值都是 GC 根。
for job in &self.job_queue {
crate::promise::for_each_job_value(job, &mut f);
}
// Atomics waiter 表登记的 promise 是 GC 根(run 内跨调用存活)。
for promises in self.atomics_waiters.values() {
for p in promises {
f(*p);
}
}
for iter in &self.iters.for_in_iters {
if iter.is_null() {
continue;
}
// SAFETY: for_in_iters 存放堆上迭代器体,VM 表独占持有。
unsafe {
for (v, _si) in (*(*iter)).keys.iter() {
f(*v);
}
}
}
f(JsValue::from_js_object(self.session.global_object().as_ptr() as *mut JsObject));
}
/// GC 指针重写(session 搬移后调用)。与 `for_each_value` 字段一一对应。
pub(crate) fn rewrite_values(&mut self, mut rewrite: impl FnMut(JsValue) -> JsValue) {
for value in &mut self.regs {
*value = rewrite(*value);
}
for frame in &mut self.frames {
frame.saved_this = rewrite(frame.saved_this);
frame.saved_new_target = rewrite(frame.saved_new_target);
frame.callee = rewrite(frame.callee);
frame.constructed_this = frame.constructed_this.map(&mut rewrite);
}
for v in &mut self.save_stack {
*v = rewrite(*v);
}
for v in &mut self.spill_stack {
*v = rewrite(*v);
}
for cell_vec in &mut self.cell_stack {
for &mut cell_ptr in cell_vec.iter_mut() {
if cell_ptr.is_null() {
continue;
}
// SAFETY: cell 由 session_epoch 分配,本 session 内指针有效。
let cell = unsafe { &mut *cell_ptr };
cell.value = rewrite(cell.value);
}
}
self.exception_value = self.exception_value.map(&mut rewrite);
self.pending_exception = self.pending_exception.map(&mut rewrite);
self.last_uncaught_value = self.last_uncaught_value.map(&mut rewrite);
self.pending_length_exception = self.pending_length_exception.map(&mut rewrite);
for cached in self.template_objects.values_mut() {
*cached = rewrite(*cached);
}
self.pending_completion = self.pending_completion.map(|completion| match completion {
Completion::Return {
value,
remaining_finally,
for_of_count,
for_in_count,
} => Completion::Return {
value: rewrite(value),
remaining_finally,
for_of_count,
for_in_count,
},
other => other,
});
self.generator_suspended = self.generator_suspended.map(&mut rewrite);
self.delegated_iterator = self.delegated_iterator.map(&mut rewrite);
self.async_context = self.async_context.map(&mut rewrite);
self.async_gen_context = self.async_gen_context.map(&mut rewrite);
// 在途异步逃出的 promise/完成值/剩余迭代器随 sweep 重写(与 mark 段一一对应)。
self.pending_async_escape = self.pending_async_escape.as_mut().map(|pend| {
let completion = match pend.completion {
Completion::Return {
value,
remaining_finally,
for_of_count,
for_in_count,
} => Completion::Return {
value: rewrite(value),
remaining_finally,
for_of_count,
for_in_count,
},
other => other,
};
super::PendingAsyncEscape {
close_promise: rewrite(pend.close_promise),
completion,
remaining: pend.remaining.iter().copied().map(&mut rewrite).collect(),
}
});
self.inline_callee = self.inline_callee.map(&mut rewrite);
for entry in &mut self.iters.for_of_iters {
entry.iterator = rewrite(entry.iterator);
entry.last_result = rewrite(entry.last_result);
entry.fast_value = rewrite(entry.fast_value);
}
// 微任务队列中的值随 sweep 重写。
for job in &mut self.job_queue {
crate::promise::rewrite_job_values(job, &mut rewrite);
}
// Atomics waiter 表中的 promise 随 sweep 重写(与 mark 段一一对应)。
for promises in self.atomics_waiters.values_mut() {
for p in promises {
*p = rewrite(*p);
}
}
for iter in &mut self.iters.for_in_iters {
if iter.is_null() {
continue;
}
// SAFETY: for_in_iters 存放堆上迭代器体,VM 表独占持有。
unsafe {
for (v, _si) in (*(*iter)).keys.iter_mut() {
*v = rewrite(*v);
}
}
}
let global_ptr = self.session.global_object().as_ptr() as *mut JsObject;
if !global_ptr.is_null() {
// SAFETY: KernelSession 在 VM 生命周期内拥有 global_object。
unsafe {
(*global_ptr).rewrite_object_values(rewrite);
}
}
}
/// GC 根统一枚举入口:遍历的字段清单与 `rewrite_values` 一一对应。
pub(crate) fn for_each_root(&self, f: impl FnMut(JsValue)) {
self.for_each_value(f);
}
/// 执行一次 session GC(是否真正回收由 `SessionGc` 的水位门控决定)。
///
/// `session_gc` 经 `mem::take` 借出后再放回:收集需要 `&mut Vm`,而
/// `SessionGc` 是 `Vm` 的内部字段,借出以避开借用冲突。
pub(crate) fn maybe_collect_session_gc(&mut self) {
let mut session_gc = std::mem::take(&mut self.gc_state.session_gc);
session_gc.maybe_collect(self);
self.gc_state.session_gc = session_gc;
}
/// 执行期字符串阈值回收:仅回收 session 字符串(跳过对象搬移)。热路径只在
/// 超阈值后进入,`mem::take` 不承担每次分配的开销。
pub(crate) fn maybe_collect_session_strings(&mut self) {
let mut session_gc = std::mem::take(&mut self.gc_state.session_gc);
session_gc.maybe_collect_strings_only(self);
self.gc_state.session_gc = session_gc;
}
/// 门控子句二:三型状态盒(生成器/异步函数/异步生成器)的
/// `suspended.for_in_iters` 是否非空。
///
/// 门控是保守门:for-in 迭代器活跃/挂起期间拦下执行期收集;
/// 挂起帧把迭代器搬入状态盒(`vm.iters` 不可见),故须逐状态盒扫描。
///
/// # 边界与前提
/// - 扫描 session 对象表——挂起状态盒宿主对象在表内;
/// - 挂起帧的 keys 经状态盒边收为 GC 根(被枚举对象不误释放),迭代器
/// 体是堆上 Box,故按指针扫 `for_in_iters`;
/// - 保守口径:死对象的状态盒同样计入(持挂起 for-in 的 run 放弃执行期
/// 收集),正确性优先于收益;
/// - 新增挂起态持有者须在此登记。
pub(crate) fn suspended_holds_for_in(&self) -> bool {
let holds = |&ptr: &*mut JsObject| {
if ptr.is_null() {
return false;
}
// SAFETY: 对象表登记的指针,存活期内有效。
let obj = unsafe { &*ptr };
match obj.type_tag {
JsObject::OBJ_TYPE_GENERATOR => crate::generator::generator_holds_suspended_for_in(obj),
JsObject::OBJ_TYPE_ASYNC => crate::async_func::async_holds_suspended_for_in(obj),
JsObject::OBJ_TYPE_ASYNC_GENERATOR => {
crate::async_generator::async_generator_holds_suspended_for_in(obj)
}
_ => false,
}
};
self.gc_state.session_object_ptrs.iter().any(holds)
}
/// 执行期两档收集的 dispatch 安全点入口:仅在循环顶
/// (`native_call_depth == 0`,无 builtin 局部裸指针、dispatch 未重入)
/// 由触发块调用;先门控后收集。
///
/// # 边界与前提
/// - 子句一(活跃 for-in,O(1))未过:免费重试,不设锚;
/// - 子句二(状态盒扫描,O(对象数))未过:重扫按包络增长一个阈值设锚,
/// 避免持挂起 for-in 的 run 每指令边界重扫;
/// - 门控过但无对象可回收时仍跑一轮(mark + 换新 Bump),水位同点抬高,
/// 触发间距由包络增量控制。
pub(crate) fn maybe_collect_in_run(&mut self) {
if !self.iters.for_in_iters.is_empty() {
return;
}
if self.gc_state.gc_gate_retry_alloc > self.run_alloc_bytes() {
return;
}
if self.suspended_holds_for_in() {
self.gc_state.gc_gate_retry_alloc =
self.run_alloc_bytes().saturating_add(self.gc_state.gc_threshold_cached);
return;
}
let mut session_gc = std::mem::take(&mut self.gc_state.session_gc);
session_gc.collect_in_run(self);
self.gc_state.session_gc = session_gc;
}
/// 只读访问 session GC 的统计(回收次数、存活/死亡对象数、释放字节等)。
pub fn session_gc_stats(&self) -> &SessionGc {
&self.gc_state.session_gc
}
/// 当前 session arena 中存活(已晋升)的对象数量。
pub fn session_object_count(&self) -> usize {
self.gc_state.session_object_ptrs.len()
}
/// session 当前分配的字节数(对象 + 存活字符串)。
pub fn session_bytes_allocated(&self) -> usize {
self.gc_state.session_bytes_allocated
}
/// 执行期 session 堆账目的峰值高水位(顶层指令边界采样,全量重置清零)。
pub fn session_bytes_peak(&self) -> usize {
self.gc_state.session_bytes_peak
}
/// 双 arena(epoch + session 对象)当前 chunk 已分配字节之和。
///
/// # 注意事项
/// reset 边界读 = 换新 Bump 后空 arena 恰 0(空 chunk 哨兵计 0);
/// run 中途读 = 当前 chunk 用量(非全 arena 口径——run 计量走
/// [`Self::run_alloc_bytes`],勿据此断言)。
pub fn arena_retained_bytes(&self) -> usize {
self.epoch.bump().allocated_bytes() + self.gc_state.session_epoch.allocated_bytes()
}
/// 本 run 累计分配字节的高水位:`run_alloc_bytes` 的顶层指令边界
/// 采样上界,run 边界(reset/full_reset)重起算。留存内存观测锚。
pub fn run_alloc_peak(&self) -> usize {
self.gc_state.run_alloc_peak
}
/// 本 run 累计分配字节:epoch arena + session 对象 arena + session 手工堆
/// 账目(session 串 + session 对象及其属性向量 + GC 后补回的 BigInt)。
/// 单次 run 内单调不减(执行期对象不回收、
/// 串 GC 只降手工堆账目而 arena 不减);run 边界(reset)后重新起算,
/// 供单 run 分配上限判定。
///
/// 注意:手工堆账目只在 promote/字符串分配/GC 回收点更新,执行期对象
/// 属性区(元素/属性向量扩容)增长对其不可见——上限判定须配合
/// [`Self::run_alloc_bytes_full`] 的深采样层。
pub(crate) fn run_alloc_bytes(&self) -> usize {
self.epoch.bump().allocated_bytes()
+ self.gc_state.session_epoch.allocated_bytes()
+ self.gc_state.session_bytes_allocated
}
/// 本 run 累计分配字节的全量重算版:base 只取两个 arena 计数器
/// (epoch + session 对象 arena),手工堆逐一重算——已登记对象的堆数据
/// (属性/元素向量 + upvalue 列表 + native 状态盒)、session 串、BigInt
/// 与 upvalue cell 的容量。三分量(arena / 对象堆数据 / 串-BigInt-cell)
/// 两两不相交,且不含 session 手工堆账目——无交叠不双计,重算即属性区
/// 扩容等账目盲区的兜底。
pub(crate) fn run_alloc_bytes_full(&self) -> u64 {
let mut bytes = (self.epoch.bump().allocated_bytes() + self.gc_state.session_epoch.allocated_bytes()) as u64;
for &ptr in self.gc_state.session_object_ptrs.iter() {
if ptr.is_null() {
continue;
}
// SAFETY: ptr 来自对象表登记,dispatch 安全点处仍有效。
bytes += SessionGc::object_heap_data_bytes(unsafe { &*ptr });
}
for &ptr in &self.gc_state.session_string_ptrs {
// SAFETY: ptr 来自字符串表登记,收尾前始终有效。
bytes += (std::mem::size_of::<oxide_types::object::JsString>() + unsafe { (*ptr).payload_bytes() }) as u64;
}
bytes += (self.gc_state.session_bigint_ptrs.borrow().len() * std::mem::size_of::<num_bigint::BigInt>()) as u64;
bytes += (self.gc_state.session_cell_ptrs.borrow().len() * std::mem::size_of::<Cell>()) as u64;
bytes
}
/// 无条件执行一次完整 session GC(mark + 移动式 sweep + 串/BigInt 清扫)。
///
/// # 副作用
/// - 存活对象复制进新 session arena,全部根与原生盒按转发表重写;
/// `session_bytes_allocated` 重置为清扫后的存活字节。
///
/// # 注意事项
/// - 须在执行外的安全点调用(无在途 builtin 局部裸指针、dispatch 未重入);
/// 执行期触发仍走水位路径,本入口供事后观测(如基准测 workload 后留存堆)。
pub fn collect_session_gc(&mut self) {
let mut session_gc = std::mem::take(&mut self.gc_state.session_gc);
session_gc.collect(self);
self.gc_state.session_gc = session_gc;
}
/// 当前 epoch 中已分配并跟踪的对象数量。对象分配统一入口 Box 化后
/// 全部对象入 session 表,epoch 表退役,恒返 0(采集点保留,恒零口径)。
pub fn epoch_object_count(&self) -> usize {
0
}
/// inline cache 命中率(0.0~1.0),用于观测 IC 预热效果。
pub fn ic_hit_rate(&self) -> f64 {
self.profiling.ic_hit_rate()
}
/// 累计执行的指令数(profiling)。
pub fn instruction_count(&self) -> u64 {
self.profiling.instruction_count
}
/// 全局 symbol 注册表中已注册的 key 数量。
pub fn symbol_registry_len(&self) -> usize {
self.symbols.registry_len()
}
/// inline cache 命中次数。
pub fn ic_hit_count(&self) -> u64 {
self.profiling.ic_hits.get()
}
/// inline cache 未命中次数。
pub fn ic_miss_count(&self) -> u64 {
self.profiling.ic_misses.get()
}
}