feat: using 声明登记释放栈指令与 GC 根

This commit is contained in:
2026-10-09 17:55:08 +00:00
parent 0df3f9d5fd
commit b5ff2c132a
10 changed files with 88 additions and 23 deletions
+3
View File
@@ -286,6 +286,9 @@ impl Vm {
OpCode::MAKE_CELL_FRESH => {
self.dispatch_make_cell_fresh(rd, instr)?;
}
OpCode::DISPOSE_REGISTER => {
self.dispatch_dispose_register(rd, a);
}
OpCode::CELL_GET => {
self.dispatch_cell_get(rd, a, b)?;
}
+8 -3
View File
@@ -8,7 +8,7 @@ use super::frames::Completion;
use super::Vm;
use crate::session_gc::SessionGc;
/// GC 根组来源:`for_each_value` 枚举的 25 个根组,变体顺序与根清单
/// GC 根组来源:`for_each_value` 枚举的 26 个根组,变体顺序与根清单
/// 的枚举顺序一致。
///
/// 变体下标是逐组计数数组(`SessionGc::root_counts`)的下标;`COUNT` 与
@@ -40,11 +40,12 @@ pub(crate) enum RootGroup {
ForInIters,
Global,
WorkerObjects,
DisposeStack,
}
impl RootGroup {
/// 根组总数(与变体数同源,绑定逐组计数数组的长度)。
pub const COUNT: usize = 25;
pub const COUNT: usize = 26;
}
impl Vm {
@@ -52,7 +53,7 @@ impl Vm {
/// 覆盖执行核心的全部 JsValue 持有点:regs/帧/各栈段/cell/在途异常与完成/
/// 挂起信号/迭代器/微任务/global/Worker 对象注册表。
///
/// 闭包接收根组来源(`RootGroup`)与根值:组是枚举顺序中的 25 根组之一,
/// 闭包接收根组来源(`RootGroup`)与根值:组是枚举顺序中的 26 根组之一,
/// 调用方按组计数(如 `SessionGc` 的逐组计数)。
pub(crate) fn for_each_value(&self, mut f: impl FnMut(RootGroup, JsValue)) {
for value in &self.regs {
@@ -82,6 +83,10 @@ impl Vm {
for &v in &self.spill_stack {
f(RootGroup::SpillStack, v);
}
// 释放栈持 using 声明的资源值:漏根 → sweep 释放 → 释放点解引用悬垂。
for &v in &self.dispose_stack {
f(RootGroup::DisposeStack, v);
}
for cell_vec in &self.cell_stack {
for &cell_ptr in cell_vec {
if cell_ptr.is_null() {
+4
View File
@@ -230,6 +230,10 @@ pub struct Vm {
/// VM 级 spill 栈。`CallFrame.spill_offset` 定位本帧区:调用子函数时从边界后分配,
/// 帧恢复时截断到边界,子函数 spill 数据随帧丢弃。
pub(crate) spill_stack: Vec<JsValue>,
/// using/await using 声明的资源释放栈:DISPOSE_REGISTER 把资源值压入,
/// 作用域出口点逆序释放(释放点由后续指令族承担)。栈内 JsValue 是 GC 根
/// (`for_each_value` 遍历);run 边界清空,防跨 run 残留条目在下一 run 被误释放。
pub(crate) dispose_stack: Vec<JsValue>,
/// 本次 native 调用的 spill 溢出实参区:`spill_stack[base..base+count)`。
/// 实参数超过寄存器窗口(253)时,窗口外的实参转存 spill 栈(GC 根),
/// native 侧经 `VmHost::native_arg_count`/`native_arg_at` 读取。仅在一次
+12
View File
@@ -649,6 +649,18 @@ impl Vm {
self.iters.pop_for_in();
}
/// using/await using 声明登记:把资源值(rd 槽)压入释放栈。
///
/// a 槽是释放提示(0 = 同步、1 = 异步),登记期原样保留供释放点分派;
/// 本指令只登记,释放时机由作用域出口点决定。
///
/// # 副作用
/// - 释放栈增长(栈内 JsValue 是 GC 根,run 边界清空)。
pub(crate) fn dispatch_dispose_register(&mut self, rd: usize, a: usize) {
vm_trace!("DISPOSE_REGISTER rd={} hint={}", rd, a);
self.dispose_stack.push(self.regs[rd]);
}
pub(crate) fn dispatch_for_of_init(&mut self, a: usize) -> Result<(), String> {
vm_trace!("FOR_OF_INIT r{}={:?}", a, self.regs[a]);
let iterable = self.regs[a];
+19
View File
@@ -1143,4 +1143,23 @@ mod tests {
vm.drain_microtasks();
assert!(vm.job_queue.is_empty());
}
#[test]
fn using_declaration_registers_dispose_stack() {
// using 声明经 DISPOSE_REGISTER 把资源值压入释放栈;run 边界(下次 run
// 入口)清栈,上一 run 的残留条目不得跨 run 可见。
let mut vm = Vm::new();
let module = Arc::new(compile("function f() { using x = { a: 1 }; } f();"));
vm.run(&module).expect("run");
assert_eq!(vm.dispose_stack.len(), 1, "using 声明应登记一个资源值");
assert!(vm.dispose_stack[0].is_object(), "登记值应为资源对象");
// 二次 run 入口清栈后重新登记:残留条目不跨 run 存活。
vm.run(&module).expect("run2");
assert_eq!(vm.dispose_stack.len(), 1, "二次 run 应恰好登记一条新条目");
// 执行状态清空同样清释放栈。
vm.clear_execution_state();
assert!(vm.dispose_stack.is_empty(), "执行状态清空应清空释放栈");
}
}
+5
View File
@@ -130,6 +130,7 @@ impl Vm {
saved_immutables_stack: Vec::new(),
save_stack: Vec::new(),
spill_stack: Vec::new(),
dispose_stack: Vec::new(),
native_overflow_base: 0,
native_overflow_count: 0,
try_stack: Vec::new(),
@@ -291,6 +292,7 @@ impl Vm {
saved_immutables_stack: Vec::new(),
save_stack: Vec::new(),
spill_stack: Vec::new(),
dispose_stack: Vec::new(),
native_overflow_base: 0,
native_overflow_count: 0,
try_stack: Vec::new(),
@@ -515,6 +517,9 @@ impl Vm {
self.saved_immutables_stack.clear();
self.save_stack.clear();
self.spill_stack.clear();
// 释放栈条目持资源值(session 对象指针):跨 run/reset 残留会在下一 run
// 被误释放或悬垂,随执行状态一并清空。
self.dispose_stack.clear();
self.native_overflow_base = 0;
self.native_overflow_count = 0;
// native pack 实参区上界与溢出描述符同构:仅一次 native 调用期间有效,