feat: GC mark 播种按 24 根组计数,修正 roots 日志口径

This commit is contained in:
2026-10-06 23:32:28 +00:00
parent 6b960ea45a
commit add910a504
7 changed files with 190 additions and 69 deletions
Generated
+18 -18
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_builtins" name = "oxide_builtins"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"chrono", "chrono",
"fast-float", "fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_bytecode" name = "oxide_bytecode"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxide_log", "oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cfg" name = "oxide_cfg"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_emit", "oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cli" name = "oxide_cli"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"bumpalo", "bumpalo",
@@ -1225,7 +1225,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_code_cache" name = "oxide_code_cache"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"lru", "lru",
"oxide_bytecode", "oxide_bytecode",
@@ -1234,7 +1234,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_compiler" name = "oxide_compiler"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1253,7 +1253,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_dce" name = "oxide_dce"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1267,7 +1267,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_emit" name = "oxide_emit"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxc_allocator", "oxc_allocator",
@@ -1282,7 +1282,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_ir" name = "oxide_ir"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_log", "oxide_log",
@@ -1291,7 +1291,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_kernel" name = "oxide_kernel"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1306,7 +1306,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_liveness" name = "oxide_liveness"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1318,7 +1318,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_log" name = "oxide_log"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-appender", "tracing-appender",
@@ -1327,7 +1327,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_parser" name = "oxide_parser"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1342,7 +1342,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_regalloc" name = "oxide_regalloc"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1358,7 +1358,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_runtime_api" name = "oxide_runtime_api"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
@@ -1370,7 +1370,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_test262" name = "oxide_test262"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1387,7 +1387,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_types" name = "oxide_types"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"hashbrown 0.14.5", "hashbrown 0.14.5",
@@ -1399,7 +1399,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_vm" name = "oxide_vm"
version = "0.8.45" version = "0.8.46"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"chrono", "chrono",
+1 -1
View File
@@ -22,7 +22,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.8.45" version = "0.8.46"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
rust-version = "1.80" rust-version = "1.80"
+13 -9
View File
@@ -2,6 +2,7 @@
use oxide_types::value::JsValue; use oxide_types::value::JsValue;
use crate::vm::RootGroup;
use crate::vm::Vm; use crate::vm::Vm;
use crate::vm_warn; use crate::vm_warn;
@@ -80,7 +81,10 @@ impl Vm {
} }
/// 供外部遍历微任务队列(GC mark/rewrite 用)。 /// 供外部遍历微任务队列(GC mark/rewrite 用)。
pub(crate) fn for_each_job_value(job: &Microtask, mut f: impl FnMut(JsValue)) { ///
/// `group` 是调用方传入的根组来源(微任务队列属 `JobQueue` 组),原样透传给
/// 内层闭包。
pub(crate) fn for_each_job_value(job: &Microtask, group: RootGroup, mut f: impl FnMut(RootGroup, JsValue)) {
match job { match job {
Microtask::Reaction { Microtask::Reaction {
handler, handler,
@@ -89,10 +93,10 @@ pub(crate) fn for_each_job_value(job: &Microtask, mut f: impl FnMut(JsValue)) {
reject, reject,
.. ..
} => { } => {
f(*handler); f(group, *handler);
f(*argument); f(group, *argument);
f(*resolve); f(group, *resolve);
f(*reject); f(group, *reject);
} }
Microtask::Thenable { Microtask::Thenable {
thenable, thenable,
@@ -100,10 +104,10 @@ pub(crate) fn for_each_job_value(job: &Microtask, mut f: impl FnMut(JsValue)) {
resolve, resolve,
reject, reject,
} => { } => {
f(*thenable); f(group, *thenable);
f(*then); f(group, *then);
f(*resolve); f(group, *resolve);
f(*reject); f(group, *reject);
} }
} }
} }
+44 -2
View File
@@ -8,6 +8,7 @@ use oxide_types::value::JsValue;
use rustc_hash::FxBuildHasher; use rustc_hash::FxBuildHasher;
use crate::native_box_dispatch; use crate::native_box_dispatch;
use crate::vm::RootGroup;
use crate::vm::Vm; 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};
@@ -30,6 +31,8 @@ pub struct SessionGc {
pub last_collection_duration_us: u64, pub last_collection_duration_us: u64,
pub max_collection_duration_us: u64, pub max_collection_duration_us: u64,
pub min_collection_duration_us: u64, pub min_collection_duration_us: u64,
/// 最近一次 mark 播种的逐组根值计数(下标为 `RootGroup` 变体下标,和为根值总数)。
pub root_counts: [u64; RootGroup::COUNT],
pub(crate) mark_stack: Vec<*mut JsObject>, pub(crate) mark_stack: Vec<*mut JsObject>,
pub(crate) live_strings: HashSet<*mut JsString, FxBuildHasher>, pub(crate) live_strings: HashSet<*mut JsString, FxBuildHasher>,
pub(crate) live_bigints: HashSet<*mut num_bigint::BigInt, FxBuildHasher>, pub(crate) live_bigints: HashSet<*mut num_bigint::BigInt, FxBuildHasher>,
@@ -375,12 +378,19 @@ impl SessionGc {
/// 字符串边走 rope 闭包传播(Cons 子节点与扁平化产物),BigInt 边直接入 /// 字符串边走 rope 闭包传播(Cons 子节点与扁平化产物),BigInt 边直接入
/// 存活集。roots 由 `Vm::for_each_root` 枚举。只置位不搬移,调用前须先 /// 存活集。roots 由 `Vm::for_each_root` 枚举。只置位不搬移,调用前须先
/// `clear_all_marks` 清位。 /// `clear_all_marks` 清位。
///
/// # 副作用
/// - `root_counts` 在播种开头重置、按根组累加(和为根值总数)。
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()); // 播种开头重置逐组计数:计数器恒反映最近一次 mark 的播种画像。
self.root_counts = [0; RootGroup::COUNT];
let mut seeds = Vec::new(); let mut seeds = Vec::new();
let mut string_seeds: Vec<*mut JsString> = Vec::new(); let mut string_seeds: Vec<*mut JsString> = Vec::new();
let mut bigint_seeds: Vec<*mut num_bigint::BigInt> = Vec::new(); let mut bigint_seeds: Vec<*mut num_bigint::BigInt> = Vec::new();
vm.for_each_root(|root| { let counts = &mut self.root_counts;
vm.for_each_root(|group, root| {
counts[group as usize] += 1;
if root.is_object() { if root.is_object() {
seeds.push(root.as_js_object_ptr()); seeds.push(root.as_js_object_ptr());
} else if root.is_string() { } else if root.is_string() {
@@ -390,6 +400,37 @@ impl SessionGc {
} }
}); });
// 根数口径是 `for_each_root` 枚举出的根值总数(24 组之和)。
let root_total: u64 = self.root_counts.iter().sum();
vm_debug!("[GC] mark phase: {} roots", root_total);
vm_debug!(
"[GC] mark phase root groups: regs={}, immutables={}, frames={}, save_stack={}, spill_stack={}, cell_stack={}, exception_value={}, pending_exception={}, last_uncaught={}, pending_length_exception={}, pending_completion={}, generator_suspended={}, delegated_iterator={}, async_context={}, async_gen_context={}, pending_async_escape={}, inline_callee={}, template_objects={}, number_to_string_cache={}, for_of_iters={}, job_queue={}, atomics_waiters={}, for_in_iters={}, global={}",
self.root_counts[RootGroup::Regs as usize],
self.root_counts[RootGroup::Immutables as usize],
self.root_counts[RootGroup::Frames as usize],
self.root_counts[RootGroup::SaveStack as usize],
self.root_counts[RootGroup::SpillStack as usize],
self.root_counts[RootGroup::CellStack as usize],
self.root_counts[RootGroup::ExceptionValue as usize],
self.root_counts[RootGroup::PendingException as usize],
self.root_counts[RootGroup::LastUncaught as usize],
self.root_counts[RootGroup::PendingLengthException as usize],
self.root_counts[RootGroup::PendingCompletion as usize],
self.root_counts[RootGroup::GeneratorSuspended as usize],
self.root_counts[RootGroup::DelegatedIterator as usize],
self.root_counts[RootGroup::AsyncContext as usize],
self.root_counts[RootGroup::AsyncGenContext as usize],
self.root_counts[RootGroup::PendingAsyncEscape as usize],
self.root_counts[RootGroup::InlineCallee as usize],
self.root_counts[RootGroup::TemplateObjects as usize],
self.root_counts[RootGroup::NumberToStringCache as usize],
self.root_counts[RootGroup::ForOfIters as usize],
self.root_counts[RootGroup::JobQueue as usize],
self.root_counts[RootGroup::AtomicsWaiters as usize],
self.root_counts[RootGroup::ForInIters as usize],
self.root_counts[RootGroup::Global as usize],
);
let Self { let Self {
mark_stack: stack, mark_stack: stack,
live_strings, live_strings,
@@ -1052,6 +1093,7 @@ impl Default for SessionGc {
last_collection_duration_us: 0, last_collection_duration_us: 0,
max_collection_duration_us: 0, max_collection_duration_us: 0,
min_collection_duration_us: u64::MAX, min_collection_duration_us: u64::MAX,
root_counts: [0; RootGroup::COUNT],
mark_stack: Vec::new(), mark_stack: Vec::new(),
live_strings: HashSet::default(), live_strings: HashSet::default(),
live_bigints: HashSet::default(), live_bigints: HashSet::default(),
+38 -5
View File
@@ -8,7 +8,7 @@ use oxide_types::value::JsValue;
use std::sync::Arc; use std::sync::Arc;
use super::*; use super::*;
use crate::vm::{CallFrame, FrameContinuation}; use crate::vm::{CallFrame, FrameContinuation, RootGroup};
use oxide_builtins::{array_buffer, data_view, disposable_stack, map, module, set, typed_array}; use oxide_builtins::{array_buffer, data_view, disposable_stack, map, module, set, typed_array};
use oxide_emit::module::{ModuleKind, ModuleSourceLoader, ResolvedModule}; use oxide_emit::module::{ModuleKind, ModuleSourceLoader, ResolvedModule};
use oxide_runtime_api::NativeResult; use oxide_runtime_api::NativeResult;
@@ -34,7 +34,7 @@ fn uncaught_value_is_gc_root() {
let session = obj; let session = obj;
vm.last_uncaught_value = Some(JsValue::from_js_object(session)); vm.last_uncaught_value = Some(JsValue::from_js_object(session));
let mut roots = Vec::new(); let mut roots = Vec::new();
vm.for_each_root(|v| roots.push(v)); vm.for_each_root(|_g, v| roots.push(v));
assert!(has_ptr(&roots, session)); assert!(has_ptr(&roots, session));
} }
@@ -45,7 +45,7 @@ fn pending_length_exception_is_gc_root() {
let session = obj; let session = obj;
vm.pending_length_exception = Some(JsValue::from_js_object(session)); vm.pending_length_exception = Some(JsValue::from_js_object(session));
let mut roots = Vec::new(); let mut roots = Vec::new();
vm.for_each_root(|v| roots.push(v)); vm.for_each_root(|_g, v| roots.push(v));
assert!(has_ptr(&roots, session)); assert!(has_ptr(&roots, session));
} }
@@ -62,7 +62,7 @@ fn suspended_signal_fields_are_roots() {
vm.async_context = Some(JsValue::from_js_object(b_s)); vm.async_context = Some(JsValue::from_js_object(b_s));
vm.async_gen_context = Some(JsValue::from_js_object(c_s)); vm.async_gen_context = Some(JsValue::from_js_object(c_s));
let mut roots = Vec::new(); let mut roots = Vec::new();
vm.for_each_root(|v| roots.push(v)); vm.for_each_root(|_g, v| roots.push(v));
assert!(has_ptr(&roots, a_s)); assert!(has_ptr(&roots, a_s));
assert!(has_ptr(&roots, b_s)); assert!(has_ptr(&roots, b_s));
assert!(has_ptr(&roots, c_s)); assert!(has_ptr(&roots, c_s));
@@ -122,7 +122,7 @@ fn gc_roots_contains_registers_frames_and_root_roots() {
}); });
let mut roots = Vec::new(); let mut roots = Vec::new();
vm.for_each_root(|v| roots.push(v)); vm.for_each_root(|_g, v| roots.push(v));
assert!(has_ptr(&roots, root_session)); assert!(has_ptr(&roots, root_session));
assert!(has_ptr(&roots, frame_session)); assert!(has_ptr(&roots, frame_session));
assert!(has_ptr(&roots, this_session)); assert!(has_ptr(&roots, this_session));
@@ -1892,3 +1892,36 @@ fn promise_promote_migrates_reject_reactions_to_clone() {
"晋升前登记的拒绝消费者应在拒绝走克隆后被触发" "晋升前登记的拒绝消费者应在拒绝走克隆后被触发"
); );
} }
/// mark 播种逐组计数:新 VM 加 1 个寄存器根后,Regs 组 256(全部寄存器槽,
/// 含未定义槽)、Global 组 1(global 对象)、9 个单槽侧通道组各 1(空时经
/// `unwrap_or(undefined)` 各产出一个未定义根值)、总和 266,其余组为 0。
#[test]
fn mark_counts_roots_by_group() {
let mut vm = Vm::new();
vm.regs[0] = JsValue::from_js_object(plain_object(&mut vm));
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
gc.mark(&vm);
vm.gc_state.session_gc = gc;
let counts = &vm.gc_state.session_gc.root_counts;
assert_eq!(counts[RootGroup::Regs as usize], 256, "寄存器组应计满 256 槽");
assert_eq!(counts[RootGroup::Global as usize], 1, "global 组应计 global 对象");
// 9 个单槽侧通道:空 VM 时各经 unwrap_or(undefined) 产出一个未定义根值。
for group in [
RootGroup::ExceptionValue,
RootGroup::PendingException,
RootGroup::LastUncaught,
RootGroup::PendingLengthException,
RootGroup::GeneratorSuspended,
RootGroup::DelegatedIterator,
RootGroup::AsyncContext,
RootGroup::AsyncGenContext,
RootGroup::InlineCallee,
] {
assert_eq!(counts[group as usize], 1, "侧通道组 {group:?} 应各计 1");
}
// 计数非负:11 个非零组之和恰为 266,总和断言同时钉死其余 13 组为 0。
let total: u64 = counts.iter().sum();
assert_eq!(total, 266, "24 组之和应为根值总数");
}
+75 -34
View File
@@ -8,13 +8,54 @@ use super::frames::Completion;
use super::Vm; use super::Vm;
use crate::session_gc::SessionGc; use crate::session_gc::SessionGc;
/// GC 根组来源:`for_each_value` 枚举的 24 个根组,变体顺序与根清单
/// 的枚举顺序一致。
///
/// 变体下标是逐组计数数组(`SessionGc::root_counts`)的下标;`COUNT` 与
/// 变体数同源,新增根组须同步加变体并更新 `COUNT`,计数数组长度随之跟随。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RootGroup {
Regs,
Immutables,
Frames,
SaveStack,
SpillStack,
CellStack,
ExceptionValue,
PendingException,
LastUncaught,
PendingLengthException,
PendingCompletion,
GeneratorSuspended,
DelegatedIterator,
AsyncContext,
AsyncGenContext,
PendingAsyncEscape,
InlineCallee,
TemplateObjects,
NumberToStringCache,
ForOfIters,
JobQueue,
AtomicsWaiters,
ForInIters,
Global,
}
impl RootGroup {
/// 根组总数(与变体数同源,绑定逐组计数数组的长度)。
pub const COUNT: usize = 24;
}
impl Vm { impl Vm {
/// GC 根收集的统一遍历(对象与字符串都产出)。 /// GC 根收集的统一遍历(对象与字符串都产出)。
/// 覆盖执行核心的全部 JsValue 持有点:regs/帧/各栈段/cell/在途异常与完成/ /// 覆盖执行核心的全部 JsValue 持有点:regs/帧/各栈段/cell/在途异常与完成/
/// 挂起信号/迭代器/微任务/global。 /// 挂起信号/迭代器/微任务/global。
pub(crate) fn for_each_value(&self, mut f: impl FnMut(JsValue)) { ///
/// 闭包接收根组来源(`RootGroup`)与根值:组是枚举顺序中的 24 根组之一,
/// 调用方按组计数(如 `SessionGc` 的逐组计数)。
pub(crate) fn for_each_value(&self, mut f: impl FnMut(RootGroup, JsValue)) {
for value in &self.regs { for value in &self.regs {
f(*value); f(RootGroup::Regs, *value);
} }
// 各代际 immutables 缓存含 session BigInt(new_bigint 分配),未入根则被 // 各代际 immutables 缓存含 session BigInt(new_bigint 分配),未入根则被
// sweep 释放 → 常量池加载时悬垂。perm 字符串无害(不在 session 集合中)。 // sweep 释放 → 常量池加载时悬垂。perm 字符串无害(不在 session 集合中)。
@@ -22,23 +63,23 @@ impl Vm {
for once_lock in &table.immutables { for once_lock in &table.immutables {
if let Some(immutable_vec) = once_lock.get() { if let Some(immutable_vec) = once_lock.get() {
for &value in immutable_vec.iter() { for &value in immutable_vec.iter() {
f(value); f(RootGroup::Immutables, value);
} }
} }
} }
} }
for frame in &self.frames { for frame in &self.frames {
f(frame.saved_this); f(RootGroup::Frames, frame.saved_this);
f(frame.saved_new_target); f(RootGroup::Frames, frame.saved_new_target);
f(frame.callee); f(RootGroup::Frames, frame.callee);
f(frame.constructed_this.unwrap_or(JsValue::undefined())); f(RootGroup::Frames, frame.constructed_this.unwrap_or(JsValue::undefined()));
} }
for &v in &self.save_stack { for &v in &self.save_stack {
f(v); f(RootGroup::SaveStack, v);
} }
// spill 栈是 session GC 根(漏根 → 溢出值被回收 → use-after-free)。 // spill 栈是 session GC 根(漏根 → 溢出值被回收 → use-after-free)。
for &v in &self.spill_stack { for &v in &self.spill_stack {
f(v); f(RootGroup::SpillStack, v);
} }
for cell_vec in &self.cell_stack { for cell_vec in &self.cell_stack {
for &cell_ptr in cell_vec { for &cell_ptr in cell_vec {
@@ -46,56 +87,56 @@ impl Vm {
continue; continue;
} }
// SAFETY: cell 经 alloc_cell 独立堆分配,本 session 内指针有效。 // SAFETY: cell 经 alloc_cell 独立堆分配,本 session 内指针有效。
f(unsafe { &*cell_ptr }.value); f(RootGroup::CellStack, unsafe { &*cell_ptr }.value);
} }
} }
f(self.exception_value.unwrap_or(JsValue::undefined())); f(RootGroup::ExceptionValue, self.exception_value.unwrap_or(JsValue::undefined()));
f(self.pending_exception.unwrap_or(JsValue::undefined())); f(RootGroup::PendingException, self.pending_exception.unwrap_or(JsValue::undefined()));
f(self.last_uncaught_value.unwrap_or(JsValue::undefined())); f(RootGroup::LastUncaught, self.last_uncaught_value.unwrap_or(JsValue::undefined()));
f(self.pending_length_exception.unwrap_or(JsValue::undefined())); f(RootGroup::PendingLengthException, self.pending_length_exception.unwrap_or(JsValue::undefined()));
// 悬挂的 return 完成持有返回值,是 GC 根。 // 悬挂的 return 完成持有返回值,是 GC 根。
if let Some(Completion::Return { value, .. }) = self.pending_completion { if let Some(Completion::Return { value, .. }) = self.pending_completion {
f(value); f(RootGroup::PendingCompletion, value);
} }
f(self.generator_suspended.unwrap_or(JsValue::undefined())); f(RootGroup::GeneratorSuspended, self.generator_suspended.unwrap_or(JsValue::undefined()));
f(self.delegated_iterator.unwrap_or(JsValue::undefined())); f(RootGroup::DelegatedIterator, self.delegated_iterator.unwrap_or(JsValue::undefined()));
f(self.async_context.unwrap_or(JsValue::undefined())); f(RootGroup::AsyncContext, self.async_context.unwrap_or(JsValue::undefined()));
f(self.async_gen_context.unwrap_or(JsValue::undefined())); f(RootGroup::AsyncGenContext, self.async_gen_context.unwrap_or(JsValue::undefined()));
// 在途异步逃出的 promise/完成值/剩余迭代器都是 GC 根。 // 在途异步逃出的 promise/完成值/剩余迭代器都是 GC 根。
if let Some(pend) = &self.pending_async_escape { if let Some(pend) = &self.pending_async_escape {
f(pend.close_promise); f(RootGroup::PendingAsyncEscape, pend.close_promise);
if let Completion::Return { value, .. } = pend.completion { if let Completion::Return { value, .. } = pend.completion {
f(value); f(RootGroup::PendingAsyncEscape, value);
} }
for &v in &pend.remaining { for &v in &pend.remaining {
f(v); f(RootGroup::PendingAsyncEscape, v);
} }
} }
f(self.inline_callee.unwrap_or(JsValue::undefined())); f(RootGroup::InlineCallee, self.inline_callee.unwrap_or(JsValue::undefined()));
// 标签模板对象缓存:命中的模板对象是 GC 根(未根 → sweep 搬移/回收悬垂)。 // 标签模板对象缓存:命中的模板对象是 GC 根(未根 → sweep 搬移/回收悬垂)。
for &cached in self.template_objects.values() { for &cached in self.template_objects.values() {
f(cached); f(RootGroup::TemplateObjects, cached);
} }
// f64→string 缓存槽值是 session 串,是 GC 根(漏根 → 串清扫释放 → 命中悬垂)。 // f64→string 缓存槽值是 session 串,是 GC 根(漏根 → 串清扫释放 → 命中悬垂)。
for &v in &self.number_to_string_cache_vals { for &v in &self.number_to_string_cache_vals {
if v.is_string() { if v.is_string() {
f(v); f(RootGroup::NumberToStringCache, v);
} }
} }
for &entry in &self.iters.for_of_iters { for &entry in &self.iters.for_of_iters {
f(entry.iterator); f(RootGroup::ForOfIters, entry.iterator);
f(entry.last_result); f(RootGroup::ForOfIters, entry.last_result);
f(entry.fast_value); f(RootGroup::ForOfIters, entry.fast_value);
f(entry.fast_inner); f(RootGroup::ForOfIters, entry.fast_inner);
} }
// 微任务队列中的处理器/能力/值都是 GC 根。 // 微任务队列中的处理器/能力/值都是 GC 根。
for job in &self.job_queue { for job in &self.job_queue {
crate::promise::for_each_job_value(job, &mut f); crate::promise::for_each_job_value(job, RootGroup::JobQueue, &mut f);
} }
// Atomics waiter 表登记的 promise 是 GC 根(run 内跨调用存活)。 // Atomics waiter 表登记的 promise 是 GC 根(run 内跨调用存活)。
for promises in self.atomics_waiters.values() { for promises in self.atomics_waiters.values() {
for p in promises { for p in promises {
f(*p); f(RootGroup::AtomicsWaiters, *p);
} }
} }
for iter in &self.iters.for_in_iters { for iter in &self.iters.for_in_iters {
@@ -105,15 +146,15 @@ impl Vm {
// SAFETY: for_in_iters 存放堆上迭代器体,VM 表独占持有。 // SAFETY: for_in_iters 存放堆上迭代器体,VM 表独占持有。
unsafe { unsafe {
for (v, _si) in (*(*iter)).keys.iter() { for (v, _si) in (*(*iter)).keys.iter() {
f(*v); f(RootGroup::ForInIters, *v);
} }
} }
} }
f(JsValue::from_js_object(self.session.global_object().as_ptr() as *mut JsObject)); f(RootGroup::Global, JsValue::from_js_object(self.session.global_object().as_ptr() as *mut JsObject));
} }
/// GC 根统一枚举入口:遍历的字段清单与 `for_each_value` 相同。 /// GC 根统一枚举入口:遍历的字段清单与 `for_each_value` 相同。
pub(crate) fn for_each_root(&self, f: impl FnMut(JsValue)) { pub(crate) fn for_each_root(&self, f: impl FnMut(RootGroup, JsValue)) {
self.for_each_value(f); self.for_each_value(f);
} }
+1
View File
@@ -39,6 +39,7 @@ mod vmhost;
pub(crate) use frames::FrameArgs; pub(crate) use frames::FrameArgs;
pub use frames::{CallFrame, Completion, ForInIter, FrameContinuation, TryHandler}; pub use frames::{CallFrame, Completion, ForInIter, FrameContinuation, TryHandler};
pub(crate) use gc_hooks::RootGroup;
pub(crate) use inline::InlineSyncState; pub(crate) use inline::InlineSyncState;
pub(crate) use tables::TableGen; pub(crate) use tables::TableGen;